From 3a79bcde7c3dee257978479721f7f6944c70d3ab Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Thu, 21 Jul 2022 17:35:22 +0200 Subject: [PATCH] feat: add apps folder --- apps/wyatt_clean_code/.gitignore | 47 ++++ apps/wyatt_clean_code/.metadata | 30 +++ apps/wyatt_clean_code/.vscode/launch.json | 125 +++++++++ apps/wyatt_clean_code/.vscode/settings.json | 15 ++ apps/wyatt_clean_code/Makefile | 54 ++++ apps/wyatt_clean_code/README.md | 16 ++ apps/wyatt_clean_code/analysis_options.yaml | 35 +++ apps/wyatt_clean_code/android/.gitignore | 13 + .../wyatt_clean_code/android/app/build.gradle | 117 +++++++++ .../android/app/src/debug/AndroidManifest.xml | 8 + .../android/app/src/main/AndroidManifest.xml | 34 +++ .../example/wyatt_clean_code/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 ++ .../app/src/main/res/values/styles.xml | 18 ++ .../app/src/profile/AndroidManifest.xml | 8 + apps/wyatt_clean_code/android/build.gradle | 31 +++ .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + apps/wyatt_clean_code/android/settings.gradle | 11 + apps/wyatt_clean_code/assets/colors.xml | 61 +++++ apps/wyatt_clean_code/assets/l10n/intl_fr.arb | 20 ++ apps/wyatt_clean_code/l10n.yaml | 4 + apps/wyatt_clean_code/lib/bootstrap.dart | 46 ++++ .../lib/core/constants/.gitkeep | 1 + .../lib/core/dependency_injection/get_it.dart | 15 ++ .../lib/core/design_system/colors.dart | 2 + .../lib/core/design_system/sizing.dart | 235 +++++++++++++++++ .../lib/core/design_system/theme.dart | 240 ++++++++++++++++++ .../lib/core/design_system/typography.dart | 115 +++++++++ .../lib/core/enums/exception_type.dart | 7 + .../lib/core/enums/flavor.dart | 12 + .../lib/core/errors/exceptions.dart | 29 +++ .../extensions/build_context_extension.dart | 8 + .../lib/core/extensions/double_extension.dart | 9 + .../lib/core/extensions/object_extension.dart | 32 +++ .../lib/core/flavors/flavor_settings.dart | 64 +++++ .../lib/core/mixins/local_data_source.dart | 6 + .../lib/core/mixins/remote_data_source.dart | 6 + .../lib/core/routes/router.dart | 37 +++ .../lib/core/usecases/usecase.dart | 13 + .../lib/core/utils/app_bloc_observer.dart | 56 ++++ .../lib/core/utils/screen_util.dart | 107 ++++++++ .../lib/core/utils/wyatt_printer.dart | 43 ++++ .../lib/data/data_sources/local/.gitkeep | 1 + .../lib/data/data_sources/remote/.gitkeep | 1 + .../wyatt_clean_code/lib/data/models/.gitkeep | 1 + .../repositories/counter_repository_impl.dart | 24 ++ .../domain/data_sources/base_data_source.dart | 1 + .../local/base_local_data_source.dart | 3 + .../remote/base_remote_data_source.dart | 3 + .../lib/domain/entities/.gitkeep | 1 + .../domain/repositories/base_repository.dart | 1 + .../repositories/counter_repository.dart | 8 + .../usecases/counter/decrement_counter.dart | 16 ++ .../usecases/counter/increment_counter.dart | 16 ++ apps/wyatt_clean_code/lib/gen/colors.gen.dart | 195 ++++++++++++++ apps/wyatt_clean_code/lib/main.dart | 115 +++++++++ .../lib/main_development.dart | 8 + .../wyatt_clean_code/lib/main_production.dart | 8 + apps/wyatt_clean_code/lib/main_staging.dart | 8 + .../lib/presentation/features/app/app.dart | 71 ++++++ .../features/counter/blocs/counter_cubit.dart | 39 +++ .../features/counter/counter_page.dart | 11 + .../counter_page_provider.dart | 31 +++ .../counter_text_consumer.dart | 11 + .../counter/widgets/counter_base.dart | 54 ++++ .../counter/widgets/counter_text.dart | 18 ++ .../features/initial/initial_page.dart | 27 ++ .../shared/layouts/app_default_scaffold.dart | 27 ++ .../shared/state_management/.gitkeep | 1 + apps/wyatt_clean_code/pubspec.yaml | 126 +++++++++ apps/wyatt_clean_code/test/widget_test.dart | 30 +++ 79 files changed, 2643 insertions(+) create mode 100644 apps/wyatt_clean_code/.gitignore create mode 100644 apps/wyatt_clean_code/.metadata create mode 100644 apps/wyatt_clean_code/.vscode/launch.json create mode 100644 apps/wyatt_clean_code/.vscode/settings.json create mode 100644 apps/wyatt_clean_code/Makefile create mode 100644 apps/wyatt_clean_code/README.md create mode 100644 apps/wyatt_clean_code/analysis_options.yaml create mode 100644 apps/wyatt_clean_code/android/.gitignore create mode 100644 apps/wyatt_clean_code/android/app/build.gradle create mode 100644 apps/wyatt_clean_code/android/app/src/debug/AndroidManifest.xml create mode 100644 apps/wyatt_clean_code/android/app/src/main/AndroidManifest.xml create mode 100644 apps/wyatt_clean_code/android/app/src/main/kotlin/com/example/wyatt_clean_code/MainActivity.kt create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/drawable/launch_background.xml create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/values-night/styles.xml create mode 100644 apps/wyatt_clean_code/android/app/src/main/res/values/styles.xml create mode 100644 apps/wyatt_clean_code/android/app/src/profile/AndroidManifest.xml create mode 100644 apps/wyatt_clean_code/android/build.gradle create mode 100644 apps/wyatt_clean_code/android/gradle.properties create mode 100644 apps/wyatt_clean_code/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 apps/wyatt_clean_code/android/settings.gradle create mode 100644 apps/wyatt_clean_code/assets/colors.xml create mode 100644 apps/wyatt_clean_code/assets/l10n/intl_fr.arb create mode 100644 apps/wyatt_clean_code/l10n.yaml create mode 100644 apps/wyatt_clean_code/lib/bootstrap.dart create mode 100644 apps/wyatt_clean_code/lib/core/constants/.gitkeep create mode 100644 apps/wyatt_clean_code/lib/core/dependency_injection/get_it.dart create mode 100644 apps/wyatt_clean_code/lib/core/design_system/colors.dart create mode 100644 apps/wyatt_clean_code/lib/core/design_system/sizing.dart create mode 100644 apps/wyatt_clean_code/lib/core/design_system/theme.dart create mode 100644 apps/wyatt_clean_code/lib/core/design_system/typography.dart create mode 100644 apps/wyatt_clean_code/lib/core/enums/exception_type.dart create mode 100644 apps/wyatt_clean_code/lib/core/enums/flavor.dart create mode 100644 apps/wyatt_clean_code/lib/core/errors/exceptions.dart create mode 100644 apps/wyatt_clean_code/lib/core/extensions/build_context_extension.dart create mode 100644 apps/wyatt_clean_code/lib/core/extensions/double_extension.dart create mode 100644 apps/wyatt_clean_code/lib/core/extensions/object_extension.dart create mode 100644 apps/wyatt_clean_code/lib/core/flavors/flavor_settings.dart create mode 100644 apps/wyatt_clean_code/lib/core/mixins/local_data_source.dart create mode 100644 apps/wyatt_clean_code/lib/core/mixins/remote_data_source.dart create mode 100644 apps/wyatt_clean_code/lib/core/routes/router.dart create mode 100644 apps/wyatt_clean_code/lib/core/usecases/usecase.dart create mode 100644 apps/wyatt_clean_code/lib/core/utils/app_bloc_observer.dart create mode 100644 apps/wyatt_clean_code/lib/core/utils/screen_util.dart create mode 100644 apps/wyatt_clean_code/lib/core/utils/wyatt_printer.dart create mode 100644 apps/wyatt_clean_code/lib/data/data_sources/local/.gitkeep create mode 100644 apps/wyatt_clean_code/lib/data/data_sources/remote/.gitkeep create mode 100644 apps/wyatt_clean_code/lib/data/models/.gitkeep create mode 100644 apps/wyatt_clean_code/lib/data/repositories/counter_repository_impl.dart create mode 100644 apps/wyatt_clean_code/lib/domain/data_sources/base_data_source.dart create mode 100644 apps/wyatt_clean_code/lib/domain/data_sources/local/base_local_data_source.dart create mode 100644 apps/wyatt_clean_code/lib/domain/data_sources/remote/base_remote_data_source.dart create mode 100644 apps/wyatt_clean_code/lib/domain/entities/.gitkeep create mode 100644 apps/wyatt_clean_code/lib/domain/repositories/base_repository.dart create mode 100644 apps/wyatt_clean_code/lib/domain/repositories/counter_repository.dart create mode 100644 apps/wyatt_clean_code/lib/domain/usecases/counter/decrement_counter.dart create mode 100644 apps/wyatt_clean_code/lib/domain/usecases/counter/increment_counter.dart create mode 100644 apps/wyatt_clean_code/lib/gen/colors.gen.dart create mode 100644 apps/wyatt_clean_code/lib/main.dart create mode 100644 apps/wyatt_clean_code/lib/main_development.dart create mode 100644 apps/wyatt_clean_code/lib/main_production.dart create mode 100644 apps/wyatt_clean_code/lib/main_staging.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/app/app.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/counter/blocs/counter_cubit.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/counter/counter_page.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_page_provider.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_text_consumer.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_base.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_text.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/features/initial/initial_page.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/shared/layouts/app_default_scaffold.dart create mode 100644 apps/wyatt_clean_code/lib/presentation/shared/state_management/.gitkeep create mode 100644 apps/wyatt_clean_code/pubspec.yaml create mode 100644 apps/wyatt_clean_code/test/widget_test.dart diff --git a/apps/wyatt_clean_code/.gitignore b/apps/wyatt_clean_code/.gitignore new file mode 100644 index 0000000..a8e938c --- /dev/null +++ b/apps/wyatt_clean_code/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/apps/wyatt_clean_code/.metadata b/apps/wyatt_clean_code/.metadata new file mode 100644 index 0000000..2544bab --- /dev/null +++ b/apps/wyatt_clean_code/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: android + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/apps/wyatt_clean_code/.vscode/launch.json b/apps/wyatt_clean_code/.vscode/launch.json new file mode 100644 index 0000000..40a2104 --- /dev/null +++ b/apps/wyatt_clean_code/.vscode/launch.json @@ -0,0 +1,125 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch development", + "request": "launch", + "type": "dart", + "program": "lib/main_development.dart", + "args": [ + "--flavor", + "development", + "--target", + "lib/main_development.dart" + ], + "flutterMode": "debug" + }, + { + "name": "Launch development in profile mode", + "request": "launch", + "type": "dart", + "program": "lib/main_development.dart", + "args": [ + "--flavor", + "development", + "--target", + "lib/main_development.dart" + ], + "flutterMode": "profile" + }, + { + "name": "Launch development in release mode", + "request": "launch", + "type": "dart", + "program": "lib/main_development.dart", + "args": [ + "--flavor", + "development", + "--target", + "lib/main_development.dart" + ], + "flutterMode": "release" + }, + { + "name": "Launch staging", + "request": "launch", + "type": "dart", + "program": "lib/main_staging.dart", + "args": [ + "--flavor", + "staging", + "--target", + "lib/main_staging.dart" + ], + "flutterMode": "debug" + }, + { + "name": "Launch staging in profile mode", + "request": "launch", + "type": "dart", + "program": "lib/main_staging.dart", + "args": [ + "--flavor", + "staging", + "--target", + "lib/main_staging.dart" + ], + "flutterMode": "profile" + }, + { + "name": "Launch staging in release mode", + "request": "launch", + "type": "dart", + "program": "lib/main_staging.dart", + "args": [ + "--flavor", + "staging", + "--target", + "lib/main_staging.dart" + ], + "flutterMode": "release" + }, + { + "name": "Launch production", + "request": "launch", + "type": "dart", + "program": "lib/main_production.dart", + "args": [ + "--flavor", + "production", + "--target", + "lib/main_production.dart" + ], + "flutterMode": "debug" + }, + { + "name": "Launch production in profile mode", + "request": "launch", + "type": "dart", + "program": "lib/main_production.dart", + "args": [ + "--flavor", + "production", + "--target", + "lib/main_production.dart" + ], + "flutterMode": "profile" + }, + { + "name": "Launch production in release mode", + "request": "launch", + "type": "dart", + "program": "lib/main_production.dart", + "args": [ + "--flavor", + "production", + "--target", + "lib/main_production.dart" + ], + "flutterMode": "release" + }, + ] +} \ No newline at end of file diff --git a/apps/wyatt_clean_code/.vscode/settings.json b/apps/wyatt_clean_code/.vscode/settings.json new file mode 100644 index 0000000..fa6f612 --- /dev/null +++ b/apps/wyatt_clean_code/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "dart.flutterSdkPath": ".fvm/flutter_sdk", + "bloc.newCubitTemplate.type": "equatable", + "psi-header.config": { + "blankLinesAfter": 0, + "forceToTop": true, + }, + "psi-header.templates": [ + { + "language": "*", + "template": [], + // disabled, + } + ], +} \ No newline at end of file diff --git a/apps/wyatt_clean_code/Makefile b/apps/wyatt_clean_code/Makefile new file mode 100644 index 0000000..a9a3ef9 --- /dev/null +++ b/apps/wyatt_clean_code/Makefile @@ -0,0 +1,54 @@ +.PHONY: help clean get upgrade format lint gen watch run-dev run-stg run-prod + +# Adding a help file: https://gist.github.com/prwhite/8168133#gistcomment-1313022 +help: ## This help dialog. + @IFS=$$'\n' ; \ + help_lines=(`fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//'`); \ + for help_line in $${help_lines[@]}; do \ + IFS=$$'#' ; \ + help_split=($$help_line) ; \ + help_command=`echo $${help_split[0]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \ + help_info=`echo $${help_split[2]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \ + printf "%-30s %s\n" $$help_command $$help_info ; \ + done + +clean: ## Cleans the environment. + @echo "• Cleaning the project..." + @rm -rf pubspec.lock + @flutter clean + +get: ## Gets the dependencies. + @echo "• Getting the dependencies..." + @flutter pub get + +upgrade: clean ## Upgrades dependencies. + @echo "• Upgrading dependencies..." + @flutter pub upgrade + +format: ## Formats the code. + @echo "• Formatting the code" + @dart format . --fix + +lint: ## Lints the code. + @echo "• Verifying code..." + @dart analyze . || (echo "Error in project"; exit 1) + +gen: get ## Run build_runner build (Freezed, Fluttergen, Hive etc...) + @echo "• build_runner build" + @flutter pub run build_runner build + +watch: get ## Run build_runner watch (Freezed, Fluttergen, Hive etc...) + @echo "• build_runner watch" + @flutter pub run build_runner watch + +run-dev: ## Run app in development mode + @echo "• Running the app (development)" + @flutter run --flavor development --target lib/main_development.dart + +run-stg: ## Run app in staging mode + @echo "• Running the app (staging)" + @flutter run --flavor staging --target lib/main_staging.dart + +run-prod: ## Run app in production mode + @echo "• Running the app (production)" + @flutter run --flavor production --target lib/main_production.dart \ No newline at end of file diff --git a/apps/wyatt_clean_code/README.md b/apps/wyatt_clean_code/README.md new file mode 100644 index 0000000..27d3667 --- /dev/null +++ b/apps/wyatt_clean_code/README.md @@ -0,0 +1,16 @@ +# wyatt_clean_code + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/apps/wyatt_clean_code/analysis_options.yaml b/apps/wyatt_clean_code/analysis_options.yaml new file mode 100644 index 0000000..9bdb566 --- /dev/null +++ b/apps/wyatt_clean_code/analysis_options.yaml @@ -0,0 +1,35 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter by +# Wyatt Studio, for apps packages, and plugins designed to +# encourage good coding practices. +include: package:wyatt_analysis/analysis_options.flutter.yaml + +analyzer: + exclude: + - '**/*.g.dart' + - '**/*.freezed.dart' + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/apps/wyatt_clean_code/android/.gitignore b/apps/wyatt_clean_code/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/apps/wyatt_clean_code/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/apps/wyatt_clean_code/android/app/build.gradle b/apps/wyatt_clean_code/android/app/build.gradle new file mode 100644 index 0000000..0b3ee57 --- /dev/null +++ b/apps/wyatt_clean_code/android/app/build.gradle @@ -0,0 +1,117 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.wyatt_clean_code" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + signingConfigs { + if (System.getenv("ANDROID_KEYSTORE_PATH")) { + release { + storeFile file(System.getenv("ANDROID_KEYSTORE_PATH")) + keyAlias System.getenv("ANDROID_KEYSTORE_ALIAS") + keyPassword System.getenv("ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD") + storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD") + } + } else { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + } + + flavorDimensions "default" + productFlavors { + production { + dimension "default" + applicationIdSuffix "" + manifestPlaceholders = [appName: "Wyatt Demo"] + } + staging { + dimension "default" + applicationIdSuffix ".stg" + manifestPlaceholders = [appName: "[STG] Wyatt Demo"] + } + development { + dimension "default" + applicationIdSuffix ".dev" + manifestPlaceholders = [appName: "[DEV] Wyatt Demo"] + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt') + } + debug { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/apps/wyatt_clean_code/android/app/src/debug/AndroidManifest.xml b/apps/wyatt_clean_code/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..6ce672d --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/apps/wyatt_clean_code/android/app/src/main/AndroidManifest.xml b/apps/wyatt_clean_code/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a940650 --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/apps/wyatt_clean_code/android/app/src/main/kotlin/com/example/wyatt_clean_code/MainActivity.kt b/apps/wyatt_clean_code/android/app/src/main/kotlin/com/example/wyatt_clean_code/MainActivity.kt new file mode 100644 index 0000000..466b2c9 --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/main/kotlin/com/example/wyatt_clean_code/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.wyatt_clean_code + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/apps/wyatt_clean_code/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/wyatt_clean_code/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/wyatt_clean_code/android/app/src/main/res/drawable/launch_background.xml b/apps/wyatt_clean_code/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/apps/wyatt_clean_code/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/wyatt_clean_code/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/apps/wyatt_clean_code/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/wyatt_clean_code/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/apps/wyatt_clean_code/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/wyatt_clean_code/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/apps/wyatt_clean_code/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/wyatt_clean_code/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/apps/wyatt_clean_code/android/app/src/main/res/values-night/styles.xml b/apps/wyatt_clean_code/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/wyatt_clean_code/android/app/src/main/res/values/styles.xml b/apps/wyatt_clean_code/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/apps/wyatt_clean_code/android/app/src/profile/AndroidManifest.xml b/apps/wyatt_clean_code/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..6ce672d --- /dev/null +++ b/apps/wyatt_clean_code/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/apps/wyatt_clean_code/android/build.gradle b/apps/wyatt_clean_code/android/build.gradle new file mode 100644 index 0000000..83ae220 --- /dev/null +++ b/apps/wyatt_clean_code/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/apps/wyatt_clean_code/android/gradle.properties b/apps/wyatt_clean_code/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/apps/wyatt_clean_code/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/apps/wyatt_clean_code/android/gradle/wrapper/gradle-wrapper.properties b/apps/wyatt_clean_code/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cc5527d --- /dev/null +++ b/apps/wyatt_clean_code/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/apps/wyatt_clean_code/android/settings.gradle b/apps/wyatt_clean_code/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/apps/wyatt_clean_code/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/apps/wyatt_clean_code/assets/colors.xml b/apps/wyatt_clean_code/assets/colors.xml new file mode 100644 index 0000000..f5e9c9a --- /dev/null +++ b/apps/wyatt_clean_code/assets/colors.xml @@ -0,0 +1,61 @@ + + + #FF2196F3 + + #FF0061A6 + #FFFFFFFF + #FFD0E4FF + #FF001D36 + + #FF535F70 + #FFFFFFFF + #FFD6E3F7 + #FF101C2B + + #FFBA1B1B + #FFFFFFFF + #FFFFDAD4 + #FF410001 + + #FFFDFCFF + #FF1B1B1B + #FFFDFCFF + #FF1B1B1B + #FFDFE2EB + #FF42474E + #FF73777F + #FF000000 + + #FF2F3033 + #FFF1F0F4 + #FF9CCAFF + + + #FF9CCAFF + #FF00325A + #FF00497F + #FFD0E4FF + + #FFBBC8DB + #FF253140 + #FF3C4858 + #FFD6E3F7 + + #FFFFB4A9 + #FF680003 + #FF930006 + #FFFFB4A9 + + #FF1B1B1B + #FFE2E2E6 + #FF1B1B1B + #FFE2E2E6 + #FF42474E + #FFC3C7D0 + #FF8D9199 + #FF000000 + + #FFE2E2E6 + #FF2F3033 + #FF0061A6 + \ No newline at end of file diff --git a/apps/wyatt_clean_code/assets/l10n/intl_fr.arb b/apps/wyatt_clean_code/assets/l10n/intl_fr.arb new file mode 100644 index 0000000..b95f76d --- /dev/null +++ b/apps/wyatt_clean_code/assets/l10n/intl_fr.arb @@ -0,0 +1,20 @@ +{ + "@@locale": "fr", + "counterAppBarTitle": "Compteur", + "@counterAppBarTitle": { + "description": "Texte affiché dans l'AppBar de la page Compteur" + }, + "youHavePushed": "Vous avez appuyé {count} fois sur le bouton !", + "@youHavePushed": { + "description": "Message affiché sur la page compteur", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "goToCounter": "Aller au Compteur", + "@goToCounter": { + "description": "Texte affiché dans le bouton ammenant vers la page Compteur" + } +} \ No newline at end of file diff --git a/apps/wyatt_clean_code/l10n.yaml b/apps/wyatt_clean_code/l10n.yaml new file mode 100644 index 0000000..dcb899b --- /dev/null +++ b/apps/wyatt_clean_code/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: assets/l10n +template-arb-file: intl_fr.arb +output-localization-file: app_localizations.dart +nullable-getter: false diff --git a/apps/wyatt_clean_code/lib/bootstrap.dart b/apps/wyatt_clean_code/lib/bootstrap.dart new file mode 100644 index 0000000..757794e --- /dev/null +++ b/apps/wyatt_clean_code/lib/bootstrap.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:wyatt_clean_code/core/dependency_injection/get_it.dart'; +import 'package:wyatt_clean_code/core/flavors/flavor_settings.dart'; +import 'package:wyatt_clean_code/core/utils/app_bloc_observer.dart'; +import 'package:wyatt_clean_code/core/utils/wyatt_printer.dart'; + +Future bootstrap(FutureOr Function() builder) async { + await runZonedGuarded( + () async { + WidgetsFlutterBinding.ensureInitialized(); + + FlutterError.onError = (details) { + WyattPrinter.get().e( + '', + details, + details.stack, + ); + }; + + FlavorSettings.init(); + GetItInitializer.run(); + + GoRouter.setUrlPathStrategy(UrlPathStrategy.path); + + if (!kReleaseMode) { + final env = FlavorSettings.get(); + WyattPrinter.get().i('Flavor : ${env.flavor.name}'); + } + + await BlocOverrides.runZoned( + () async => runApp(await builder()), + blocObserver: AppBlocObserver(), + ); + }, + (error, stackTrace) => WyattPrinter.get().e( + '', + error, + stackTrace, + ), + ); +} diff --git a/apps/wyatt_clean_code/lib/core/constants/.gitkeep b/apps/wyatt_clean_code/lib/core/constants/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/constants/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_clean_code/lib/core/dependency_injection/get_it.dart b/apps/wyatt_clean_code/lib/core/dependency_injection/get_it.dart new file mode 100644 index 0000000..7180ebb --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/dependency_injection/get_it.dart @@ -0,0 +1,15 @@ +import 'dart:async'; + +import 'package:get_it/get_it.dart'; + +final getIt = GetIt.I; + +abstract class GetItInitializer { + static Future init() async { + // Here, register data sources + } + + static void run() { + unawaited(init()); + } +} diff --git a/apps/wyatt_clean_code/lib/core/design_system/colors.dart b/apps/wyatt_clean_code/lib/core/design_system/colors.dart new file mode 100644 index 0000000..b48f3f8 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/design_system/colors.dart @@ -0,0 +1,2 @@ +/// Generate colors with `flutter pub run build_runner build` +export 'package:wyatt_clean_code/gen/colors.gen.dart'; diff --git a/apps/wyatt_clean_code/lib/core/design_system/sizing.dart b/apps/wyatt_clean_code/lib/core/design_system/sizing.dart new file mode 100644 index 0000000..1c4fdc0 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/design_system/sizing.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +/// Geometric progression. +abstract class AppSizing { + /// Default to 1 + static const double factor = 1; + + /// SizedBox.shrink(); + static const SizedBox empty = SizedBox.shrink(); + + /// xxs = factor * 2 + static const double xxs = factor * 2; + + /// xs = factor * 4 + static const double xs = factor * 4; + + /// s = factor * 8 + static const double s = factor * 8; + + /// m = factor * 16 + static const double m = factor * 16; + + /// l = factor * 32 + static const double l = factor * 32; + + /// xl = factor * 64 + static const double xl = factor * 64; + + /// xxl = factor * 128 + static const double xxl = factor * 128; + + /// xxs = factor * 2 + static const Gap xxsGap = Gap(xxs); + + /// xs = factor * 4 + static const Gap xsGap = Gap(xs); + + /// s = factor * 8 + static const Gap sGap = Gap(s); + + /// m = factor * 16 + static const Gap mGap = Gap(m); + + /// l = factor * 32 + static const Gap lGap = Gap(l); + + /// xl = factor * 64 + static const Gap xlGap = Gap(xl); + + /// xxl = factor * 128 + static const Gap xxlGap = Gap(xxl); + + /// xxs = factor * 2 + static const Radius xxsRadius = Radius.circular(xxs); + + /// xs = factor * 4 + static const Radius xsRadius = Radius.circular(xs); + + /// s = factor * 8 + static const Radius sRadius = Radius.circular(s); + + /// m = factor * 16 + static const Radius mRadius = Radius.circular(m); + + /// l = factor * 32 + static const Radius lRadius = Radius.circular(l); + + /// xl = factor * 64 + static const Radius xlRadius = Radius.circular(xl); + + /// xxl = factor * 128 + static const Radius xxlRadius = Radius.circular(xxl); + + /// xxs = factor * 2 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets xxsSquareInset = EdgeInsets.all(xxs); + + /// xs = factor * 4 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets xsSquareInset = EdgeInsets.all(xs); + + /// s = factor * 8 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets sSquareInset = EdgeInsets.all(s); + + /// m = factor * 16 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets mSquareInset = EdgeInsets.all(m); + + /// l = factor * 32 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets lSquareInset = EdgeInsets.all(l); + + /// xl = factor * 64 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets xlSquareInset = EdgeInsets.all(xl); + + /// xxl = factor * 128 + /// + /// A square inset offers indents content on all four sides. + /// + /// *e.g [EdgeInsets.all(value)]* + static const EdgeInsets xxlSquareInset = EdgeInsets.all(xxl); + + /// xxs = factor * 2 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets xxsSquishInset = + EdgeInsets.symmetric(horizontal: xxs, vertical: xxs / 2); + + /// xs = factor * 4 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets xsSquishInset = + EdgeInsets.symmetric(horizontal: xs, vertical: xs / 2); + + /// s = factor * 8 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets sSquishInset = + EdgeInsets.symmetric(horizontal: s, vertical: s / 2); + + /// m = factor * 16 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets mSquishInset = + EdgeInsets.symmetric(horizontal: m, vertical: m / 2); + + /// l = factor * 32 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets lSquishInset = + EdgeInsets.symmetric(horizontal: l, vertical: l / 2); + + /// xl = factor * 64 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets xlSquishInset = + EdgeInsets.symmetric(horizontal: xl, vertical: xl / 2); + + /// xxl = factor * 128 + /// + /// A squished inset reduces space top and bottom by 50%. + /// + /// *e.g [EdgeInsets.symmetric(horizontal: value, vertical: value / 2)]* + static const EdgeInsets xxlSquishInset = + EdgeInsets.symmetric(horizontal: xxl, vertical: xxl / 2); + + /// xxs = factor * 2 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets xxsStretchInset = + EdgeInsets.symmetric(vertical: xxs, horizontal: xxs / 2); + + /// xs = factor * 4 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets xsStretchInset = + EdgeInsets.symmetric(vertical: xs, horizontal: xs / 2); + + /// s = factor * 8 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets sStretchInset = + EdgeInsets.symmetric(vertical: s, horizontal: s / 2); + + /// m = factor * 16 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets mStretchInset = + EdgeInsets.symmetric(vertical: m, horizontal: m / 2); + + /// l = factor * 32 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets lStretchInset = + EdgeInsets.symmetric(vertical: l, horizontal: l / 2); + + /// xl = factor * 64 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets xlStretchInset = + EdgeInsets.symmetric(vertical: xl, horizontal: xl / 2); + + /// xxl = factor * 128 + /// + /// A stretched inset reduces space left and right by 50%. + /// + /// *e.g [EdgeInsets.symmetric(vertical: value, horizontal: value / 2)]* + static const EdgeInsets xxlStretchInset = + EdgeInsets.symmetric(vertical: xxl, horizontal: xxl / 2); +} diff --git a/apps/wyatt_clean_code/lib/core/design_system/theme.dart b/apps/wyatt_clean_code/lib/core/design_system/theme.dart new file mode 100644 index 0000000..1f079c0 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/design_system/theme.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_clean_code/core/design_system/colors.dart'; +import 'package:wyatt_clean_code/core/design_system/typography.dart'; + +const _smallTextScaleFactor = 0.80; +const _largeTextScaleFactor = 1.20; + +/// Namespace for the [ThemeData]. +class AppTheme { + /// Light `ThemeData` for UI. + static ThemeData get light => ThemeData( + colorScheme: ColorScheme.fromSwatch( + primarySwatch: ColorName.seedColor, + accentColor: ColorName.seedColorAccent, + cardColor: ColorName.lightBackground, + backgroundColor: ColorName.lightBackground, + errorColor: ColorName.lightError, + ), + appBarTheme: _appBarLightTheme, + elevatedButtonTheme: _elevatedButtonLightTheme, + outlinedButtonTheme: _outlinedButtonLightTheme, + textTheme: _textTheme(), + dialogTheme: _dialogLightTheme, + tooltipTheme: _tooltipLightTheme, + bottomSheetTheme: _bottomSheetLightTheme, + tabBarTheme: _tabBarLightTheme, + dividerTheme: _dividerLightTheme, + backgroundColor: ColorName.lightBackground, + ); + + /// dark `ThemeData` for UI. + static ThemeData get dark => ThemeData( + colorScheme: ColorScheme.fromSwatch( + primarySwatch: ColorName.seedColor, + accentColor: ColorName.darkSecondary, + cardColor: ColorName.darkBackground, + backgroundColor: ColorName.darkBackground, + errorColor: ColorName.darkError, + brightness: Brightness.dark, + ), + appBarTheme: _appBarDarkTheme, + elevatedButtonTheme: _elevatedButtonDarkTheme, + outlinedButtonTheme: _outlinedButtonDarkTheme, + textTheme: _textTheme(isDark: true), + dialogTheme: _dialogDarkTheme, + tooltipTheme: _tooltipDarkTheme, + bottomSheetTheme: _bottomSheetDarkTheme, + tabBarTheme: _tabBarDarkTheme, + dividerTheme: _dividerDarkTheme, + backgroundColor: ColorName.darkBackground, + canvasColor: ColorName.darkBackground, + ); + + /// `ThemeData` for UI for small screens. + static ThemeData get lightSmall => + light.copyWith(textTheme: _smallTextTheme()); + + /// `ThemeData` for UI for medium screens. + static ThemeData get lightMedium => + light.copyWith(textTheme: _smallTextTheme()); + + /// `ThemeData` for UI for large screens. + static ThemeData get lightLarge => + light.copyWith(textTheme: _largeTextTheme()); + + /// `ThemeData` for UI for small screens. + static ThemeData get darkSmall => + dark.copyWith(textTheme: _smallTextTheme(isDark: true)); + + /// `ThemeData` for UI for medium screens. + static ThemeData get darkMedium => + dark.copyWith(textTheme: _smallTextTheme(isDark: true)); + + /// `ThemeData` for UI for large screens. + static ThemeData get darkLarge => + dark.copyWith(textTheme: _largeTextTheme(isDark: true)); + + static TextTheme _textTheme({bool isDark = false}) => TextTheme( + headline1: AppTypography.headline1, + headline2: AppTypography.headline2, + headline3: AppTypography.headline3, + headline4: AppTypography.headline4, + headline5: AppTypography.headline5, + headline6: AppTypography.headline6, + subtitle1: AppTypography.subtitle1, + subtitle2: AppTypography.subtitle2, + bodyText1: AppTypography.bodyText1, + bodyText2: AppTypography.bodyText2, + caption: AppTypography.caption, + overline: AppTypography.overline, + button: AppTypography.button, + ).apply( + bodyColor: + isDark ? ColorName.darkOnBackground : ColorName.lightOnBackground, + displayColor: + isDark ? ColorName.darkOnBackground : ColorName.lightOnBackground, + ); + + static TextTheme _smallTextTheme({bool isDark = false}) => + _textTheme(isDark: isDark).apply(fontSizeFactor: _smallTextScaleFactor); + + static TextTheme _largeTextTheme({bool isDark = false}) => + _textTheme(isDark: isDark).apply(fontSizeFactor: _largeTextScaleFactor); + + static AppBarTheme get _appBarLightTheme => + const AppBarTheme(color: ColorName.lightPrimary); + + static AppBarTheme get _appBarDarkTheme => + const AppBarTheme(color: ColorName.darkSurfaceVariant); + + static ElevatedButtonThemeData get _elevatedButtonLightTheme => + ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), + primary: ColorName.lightPrimary, + fixedSize: const Size(208, 54), + ), + ); + + static ElevatedButtonThemeData get _elevatedButtonDarkTheme => + ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), + primary: ColorName.darkPrimary, + fixedSize: const Size(208, 54), + ), + ); + + static OutlinedButtonThemeData get _outlinedButtonLightTheme => + OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), + side: const BorderSide(color: ColorName.lightOutline, width: 2), + primary: ColorName.lightPrimary, + fixedSize: const Size(208, 54), + ), + ); + + static OutlinedButtonThemeData get _outlinedButtonDarkTheme => + OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), + side: const BorderSide(color: ColorName.darkOutline, width: 2), + primary: ColorName.darkPrimary, + fixedSize: const Size(208, 54), + ), + ); + + static TooltipThemeData get _tooltipLightTheme => const TooltipThemeData( + decoration: BoxDecoration( + color: ColorName.lightInverseSurface, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + padding: EdgeInsets.all(10), + textStyle: TextStyle(color: ColorName.lightOnInverseSurface), + ); + + static TooltipThemeData get _tooltipDarkTheme => const TooltipThemeData( + decoration: BoxDecoration( + color: ColorName.darkInverseSurface, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + padding: EdgeInsets.all(10), + textStyle: TextStyle(color: ColorName.darkOnInverseSurface), + ); + + static DialogTheme get _dialogLightTheme => DialogTheme( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ); + + static DialogTheme get _dialogDarkTheme => DialogTheme( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ); + + static BottomSheetThemeData get _bottomSheetLightTheme => + const BottomSheetThemeData( + backgroundColor: ColorName.lightBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + ); + + static BottomSheetThemeData get _bottomSheetDarkTheme => + const BottomSheetThemeData( + backgroundColor: ColorName.darkBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + ); + + static TabBarTheme get _tabBarLightTheme => const TabBarTheme( + indicator: UnderlineTabIndicator( + borderSide: BorderSide( + width: 2, + color: ColorName.lightPrimary, + ), + ), + labelColor: ColorName.lightPrimary, + unselectedLabelColor: ColorName.lightOutline, + indicatorSize: TabBarIndicatorSize.tab, + ); + + static TabBarTheme get _tabBarDarkTheme => const TabBarTheme( + indicator: UnderlineTabIndicator( + borderSide: BorderSide( + width: 2, + color: ColorName.darkPrimary, + ), + ), + labelColor: ColorName.darkPrimary, + unselectedLabelColor: ColorName.darkOutline, + indicatorSize: TabBarIndicatorSize.tab, + ); + + static DividerThemeData get _dividerLightTheme => const DividerThemeData( + space: 0, + thickness: 1, + color: ColorName.lightOutline, + ); + + static DividerThemeData get _dividerDarkTheme => const DividerThemeData( + space: 0, + thickness: 1, + color: ColorName.darkOutline, + ); +} diff --git a/apps/wyatt_clean_code/lib/core/design_system/typography.dart b/apps/wyatt_clean_code/lib/core/design_system/typography.dart new file mode 100644 index 0000000..ce1b241 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/design_system/typography.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +abstract class AppFontWeight { + /// FontWeight value of `w900` + static const FontWeight black = FontWeight.w900; + + /// FontWeight value of `w800` + static const FontWeight extraBold = FontWeight.w800; + + /// FontWeight value of `w700` + static const FontWeight bold = FontWeight.w700; + + /// FontWeight value of `w600` + static const FontWeight semiBold = FontWeight.w600; + + /// FontWeight value of `w500` + static const FontWeight medium = FontWeight.w500; + + /// FontWeight value of `w400` + static const FontWeight regular = FontWeight.w400; + + /// FontWeight value of `w300` + static const FontWeight light = FontWeight.w300; + + /// FontWeight value of `w200` + static const FontWeight extraLight = FontWeight.w200; + + /// FontWeight value of `w100` + static const FontWeight thin = FontWeight.w100; +} + +class AppTypography { + static const TextStyle _base = TextStyle( + color: Colors.black, + fontWeight: AppFontWeight.regular, + ); + + /// Headline 1 Text Style + static TextStyle get headline1 => _base.copyWith( + fontSize: 56, + fontWeight: AppFontWeight.medium, + ); + + /// Headline 2 Text Style + static TextStyle get headline2 => _base.copyWith( + fontSize: 30, + fontWeight: AppFontWeight.regular, + ); + + /// Headline 3 Text Style + static TextStyle get headline3 => _base.copyWith( + fontSize: 28, + fontWeight: AppFontWeight.regular, + ); + + /// Headline 4 Text Style + static TextStyle get headline4 => _base.copyWith( + fontSize: 22, + fontWeight: AppFontWeight.bold, + ); + + /// Headline 5 Text Style + static TextStyle get headline5 => _base.copyWith( + fontSize: 20, + fontWeight: AppFontWeight.medium, + ); + + /// Headline 6 Text Style + static TextStyle get headline6 => _base.copyWith( + fontSize: 22, + fontWeight: AppFontWeight.bold, + ); + + /// Subtitle 1 Text Style + static TextStyle get subtitle1 => _base.copyWith( + fontSize: 16, + fontWeight: AppFontWeight.bold, + ); + + /// Subtitle 2 Text Style + static TextStyle get subtitle2 => _base.copyWith( + fontSize: 14, + fontWeight: AppFontWeight.bold, + ); + + /// Body Text 1 Text Style + static TextStyle get bodyText1 => _base.copyWith( + fontSize: 18, + fontWeight: AppFontWeight.medium, + ); + + /// Body Text 2 Text Style (the default) + static TextStyle get bodyText2 => _base.copyWith( + fontSize: 16, + fontWeight: AppFontWeight.regular, + ); + + /// Caption Text Style + static TextStyle get caption => _base.copyWith( + fontSize: 14, + fontWeight: AppFontWeight.regular, + ); + + /// Overline Text Style + static TextStyle get overline => _base.copyWith( + fontSize: 16, + fontWeight: AppFontWeight.regular, + ); + + /// Button Text Style + static TextStyle get button => _base.copyWith( + fontSize: 18, + fontWeight: AppFontWeight.medium, + ); +} diff --git a/apps/wyatt_clean_code/lib/core/enums/exception_type.dart b/apps/wyatt_clean_code/lib/core/enums/exception_type.dart new file mode 100644 index 0000000..decb960 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/enums/exception_type.dart @@ -0,0 +1,7 @@ +enum AppExceptionType { + network, + api, + database, + cache, + assertion, +} diff --git a/apps/wyatt_clean_code/lib/core/enums/flavor.dart b/apps/wyatt_clean_code/lib/core/enums/flavor.dart new file mode 100644 index 0000000..2cec6bb --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/enums/flavor.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +enum Flavor { + development('dev', Colors.red), + staging('stg', Colors.blue), + production('prod', Colors.green); + + final String short; + final Color color; + + const Flavor(this.short, this.color); +} diff --git a/apps/wyatt_clean_code/lib/core/errors/exceptions.dart b/apps/wyatt_clean_code/lib/core/errors/exceptions.dart new file mode 100644 index 0000000..48d84be --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/errors/exceptions.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:wyatt_clean_code/core/enums/exception_type.dart'; + +abstract class AppException extends Equatable implements Exception { + final String message; + final AppExceptionType type; + + AppException(this.type, [String? message]) : message = message ?? type.name; + + @override + List get props => [message, type]; + + @override + String toString() => message; +} + +class ClientException extends AppException { + ClientException(super.type, [super.message]); + + @override + String toString() => 'ClientException: ${super.toString()}'; +} + +class ServerException extends AppException { + ServerException(super.type, [super.message]); + + @override + String toString() => 'ServerException: ${super.toString()}'; +} diff --git a/apps/wyatt_clean_code/lib/core/extensions/build_context_extension.dart b/apps/wyatt_clean_code/lib/core/extensions/build_context_extension.dart new file mode 100644 index 0000000..df68dfe --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/extensions/build_context_extension.dart @@ -0,0 +1,8 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +export 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +extension BuildContextX on BuildContext { + AppLocalizations get l10n => AppLocalizations.of(this); +} diff --git a/apps/wyatt_clean_code/lib/core/extensions/double_extension.dart b/apps/wyatt_clean_code/lib/core/extensions/double_extension.dart new file mode 100644 index 0000000..3b792da --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/extensions/double_extension.dart @@ -0,0 +1,9 @@ +import 'package:wyatt_clean_code/core/utils/screen_util.dart'; + +extension DoubleX on double { + double get w => ScreenUtil().setWidth(this); + + double get h => ScreenUtil().setHeight(this); + + double get sp => ScreenUtil().setSp(this); +} diff --git a/apps/wyatt_clean_code/lib/core/extensions/object_extension.dart b/apps/wyatt_clean_code/lib/core/extensions/object_extension.dart new file mode 100644 index 0000000..0dfcd1f --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/extensions/object_extension.dart @@ -0,0 +1,32 @@ +import 'package:logger/logger.dart'; +import 'package:wyatt_clean_code/core/utils/wyatt_printer.dart'; + +extension ObjectX on Object { + void log({Level level = Level.debug, String Function(Object obj)? wrap}) { + final msg = wrap != null ? wrap(this) : this; + WyattPrinter.get().log(level, msg); + } + + /// Log a message at level [Level.verbose]. + void v({String Function(Object obj)? wrap}) => + log(level: Level.verbose, wrap: wrap); + + /// Log a message at level [Level.debug]. + void d({String Function(Object obj)? wrap}) => log(wrap: wrap); + + /// Log a message at level [Level.info]. + void i({String Function(Object obj)? wrap}) => + log(level: Level.info, wrap: wrap); + + /// Log a message at level [Level.warning]. + void w({String Function(Object obj)? wrap}) => + log(level: Level.warning, wrap: wrap); + + /// Log a message at level [Level.error]. + void e({String Function(Object obj)? wrap}) => + log(level: Level.error, wrap: wrap); + + /// Log a message at level [Level.wtf]. + void wtf({String Function(Object obj)? wrap}) => + log(level: Level.wtf, wrap: wrap); +} diff --git a/apps/wyatt_clean_code/lib/core/flavors/flavor_settings.dart b/apps/wyatt_clean_code/lib/core/flavors/flavor_settings.dart new file mode 100644 index 0000000..986eeee --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/flavors/flavor_settings.dart @@ -0,0 +1,64 @@ +import 'package:wyatt_clean_code/core/enums/flavor.dart'; + +class FlavorSettings { + static FlavorSettings? _instance; + + final Flavor flavor; + + // Per flavor settings + String apiKey = ''; + + /// Banner are not display in release mode, whatever this value + bool displayBanner = true; + + FlavorSettings._(this.flavor); + + factory FlavorSettings.development() { + _instance ??= FlavorSettings._(Flavor.development); + if (_instance!.flavor != Flavor.development) { + throw Exception('Flavor already initialized in: ${_instance!.flavor}'); + } + return _instance!; + } + + factory FlavorSettings.staging() { + _instance ??= FlavorSettings._(Flavor.staging); + if (_instance!.flavor != Flavor.staging) { + throw Exception('Flavor already initialized in: ${_instance!.flavor}'); + } + return _instance!; + } + + factory FlavorSettings.production() { + _instance ??= FlavorSettings._(Flavor.production); + if (_instance!.flavor != Flavor.production) { + throw Exception('Flavor already initialized in: ${_instance!.flavor}'); + } + return _instance!; + } + + /// Returns initialized [FlavorSettings], may throw if not initialized. + static FlavorSettings get() { + if (_instance == null) { + throw Exception('Flavor not initialized!'); + } + return _instance!; + } + + /// To call after `WidgetsFlutterBinding.ensureInitialized()` + /// + /// Here you can config all the settings attributes. + static void init() { + switch (get().flavor) { + case Flavor.development: + _instance!.apiKey = 'example-dev'; + break; + case Flavor.staging: + _instance!.apiKey = 'example-stg'; + break; + case Flavor.production: + _instance!.apiKey = 'example-prod'; + break; + } + } +} diff --git a/apps/wyatt_clean_code/lib/core/mixins/local_data_source.dart b/apps/wyatt_clean_code/lib/core/mixins/local_data_source.dart new file mode 100644 index 0000000..355011f --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/mixins/local_data_source.dart @@ -0,0 +1,6 @@ +import 'package:wyatt_clean_code/domain/data_sources/local/base_local_data_source.dart'; + +mixin LocalDataSource { + /// Offline data source, for debug or cache + Local get localDataSource; +} diff --git a/apps/wyatt_clean_code/lib/core/mixins/remote_data_source.dart b/apps/wyatt_clean_code/lib/core/mixins/remote_data_source.dart new file mode 100644 index 0000000..ce2211f --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/mixins/remote_data_source.dart @@ -0,0 +1,6 @@ +import 'package:wyatt_clean_code/domain/data_sources/remote/base_remote_data_source.dart'; + +mixin RemoteDataSource { + /// Online data source, to provide data through API + Remote get remoteDataSource; +} diff --git a/apps/wyatt_clean_code/lib/core/routes/router.dart b/apps/wyatt_clean_code/lib/core/routes/router.dart new file mode 100644 index 0000000..ee99f64 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/routes/router.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/counter_page.dart'; +import 'package:wyatt_clean_code/presentation/features/initial/initial_page.dart'; + +abstract class AppRouter { + static Page defaultTransition( + BuildContext context, + GoRouterState state, + Widget child, + ) => + MaterialPage( + key: state.pageKey, + child: child, + ); + + static final List routes = [ + GoRoute( + path: '/', + name: InitialPage.pageName, + pageBuilder: (context, state) => defaultTransition( + context, + state, + const InitialPage(), + ), + ), + GoRoute( + path: '/counter', + name: CounterPage.pageName, + pageBuilder: (context, state) => defaultTransition( + context, + state, + const CounterPage(), + ), + ), + ]; +} diff --git a/apps/wyatt_clean_code/lib/core/usecases/usecase.dart b/apps/wyatt_clean_code/lib/core/usecases/usecase.dart new file mode 100644 index 0000000..565f51f --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/usecases/usecase.dart @@ -0,0 +1,13 @@ +import 'package:equatable/equatable.dart'; +import 'package:wyatt_clean_code/core/errors/exceptions.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +// ignore: one_member_abstracts +abstract class UseCase { + Future> call(Params params); +} + +class NoParams extends Equatable { + @override + List get props => []; +} diff --git a/apps/wyatt_clean_code/lib/core/utils/app_bloc_observer.dart b/apps/wyatt_clean_code/lib/core/utils/app_bloc_observer.dart new file mode 100644 index 0000000..9867e6c --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/utils/app_bloc_observer.dart @@ -0,0 +1,56 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:logger/logger.dart'; +import 'package:wyatt_clean_code/core/extensions/object_extension.dart'; + +class AppBlocObserver extends BlocObserver { + final bool printEvent; + final bool printError; + final bool printChange; + final bool printTransition; + + final Logger logger = Logger(printer: SimplePrinter()); + + AppBlocObserver({ + this.printEvent = true, + this.printError = true, + this.printTransition = true, + this.printChange = true, + }); + + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + if (printEvent) { + event?.d(wrap: (obj) => 'onEvent $event'); + } + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + if (printError) { + error.e( + wrap: (obj) => 'onError(${bloc.runtimeType}, $obj, $stackTrace)', + ); + } + super.onError(bloc, error, stackTrace); + } + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + if (printChange) { + change.d(wrap: (obj) => 'onChange(${bloc.runtimeType}, $obj)'); + } + } + + @override + void onTransition( + Bloc bloc, + Transition transition, + ) { + super.onTransition(bloc, transition); + if (printTransition) { + transition.d(wrap: (obj) => 'onTransition $obj'); + } + } +} diff --git a/apps/wyatt_clean_code/lib/core/utils/screen_util.dart b/apps/wyatt_clean_code/lib/core/utils/screen_util.dart new file mode 100644 index 0000000..8f5656a --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/utils/screen_util.dart @@ -0,0 +1,107 @@ +import 'dart:ui'; + +class ScreenUtil { + static late ScreenUtil _instance; + static const int defaultWidth = 414; + static const int defaultHeight = 896; + + /// Size of the phone in UI Design ,px + late num uiWidthPx; + late num uiHeightPx; + + /// allowFontScaling Specifies whether fonts should scale to respect Text + /// Size accessibility settings. The default is false. + late bool allowFontScaling; + + static late double _screenWidth; + static late double _screenHeight; + static late double _pixelRatio; + static late double _statusBarHeight; + static late double _bottomBarHeight; + static late double _textScaleFactor; + + factory ScreenUtil() => _instance; + + ScreenUtil._(); + + static void init({ + num width = defaultWidth, + num height = defaultHeight, + bool allowFontScaling = false, + }) { + _instance = ScreenUtil._(); + _instance.uiWidthPx = width; + _instance.uiHeightPx = height; + _instance.allowFontScaling = allowFontScaling; + _pixelRatio = window.devicePixelRatio; + _screenWidth = window.physicalSize.width; + _screenHeight = window.physicalSize.height; + _statusBarHeight = window.padding.top; + _bottomBarHeight = window.padding.bottom; + _textScaleFactor = window.textScaleFactor; + } + + /// The number of font pixels for each logical pixel. + static double get textScaleFactor => _textScaleFactor; + + /// The size of the media in logical pixels (e.g, the size of the screen). + static double get pixelRatio => _pixelRatio; + + /// The horizontal extent of this size. + static double get screenWidth => _screenWidth / _pixelRatio; + + ///The vertical extent of this size. dp + static double get screenHeight => _screenHeight / _pixelRatio; + + /// The vertical extent of this size. px + static double get screenWidthPx => _screenWidth; + + /// The vertical extent of this size. px + static double get screenHeightPx => _screenHeight; + + /// The offset from the top + static double get statusBarHeight => _statusBarHeight / _pixelRatio; + + /// The offset from the top + static double get statusBarHeightPx => _statusBarHeight; + + /// The offset from the bottom. + static double get bottomBarHeight => _bottomBarHeight; + + /// The ratio of the actual dp to the design draft px + double get scaleWidth => screenWidth / uiWidthPx; + + double get scaleHeight => + (_screenHeight - _statusBarHeight - _bottomBarHeight) / uiHeightPx; + + double get scaleText => scaleWidth; + + /// Width function + /// + /// Adapted to the device width of the UI Design. + /// Height can also be adapted according to this to ensure no deformation , + /// if you want a square + double setWidth(num width) => width * scaleWidth; + + /// Height function + /// + /// Highly adaptable to the device according to UI Design + /// It is recommended to use this method to achieve a high degree + /// of adaptation when it is found that one screen in the UI design + /// does not match the current style effect, or if there is a difference + /// in shape. + double setHeight(num height) => height * scaleHeight; + + /// FontSize function + /// + /// [fontSize] The size of the font on the UI design, in px. + /// [allowFontScaling] + double setSp(num fontSize, {bool allowFontScalingSelf = false}) => + allowFontScalingSelf + ? (allowFontScalingSelf + ? (fontSize * scaleText) + : ((fontSize * scaleText) / _textScaleFactor)) + : (allowFontScaling + ? (fontSize * scaleText) + : ((fontSize * scaleText) / _textScaleFactor)); +} diff --git a/apps/wyatt_clean_code/lib/core/utils/wyatt_printer.dart b/apps/wyatt_clean_code/lib/core/utils/wyatt_printer.dart new file mode 100644 index 0000000..9ceeb38 --- /dev/null +++ b/apps/wyatt_clean_code/lib/core/utils/wyatt_printer.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:logger/logger.dart'; + +class WyattPrinter extends LogPrinter { + WyattPrinter({this.colors = true}); + + final bool colors; + + static Logger? _instance; + + /// Returns [Logger] instance or create it if not. + static Logger get({bool colors = true}) { + _instance ??= Logger(printer: WyattPrinter(colors: colors)); + return _instance!; + } + + @override + List log(LogEvent event) { + // final classNameStr = (className != null) ? '$className ' : ''; + final messageStr = _stringifyMessage(event.message); + final errorStr = event.error != null ? 'ERROR: ${event.error}' : ''; + return ['${_labelFor(event.level)} $messageStr$errorStr']; + } + + String _labelFor(Level level) { + final prefix = PrettyPrinter.levelEmojis[level]!; + final color = PrettyPrinter.levelColors[level]!; + + return colors ? color(prefix) : prefix; + } + + String _stringifyMessage(dynamic message) { + // ignore: avoid_dynamic_calls + final finalMessage = message is Function ? message() : message; + if (finalMessage is Map || finalMessage is Iterable) { + const encoder = JsonEncoder.withIndent(null); + return encoder.convert(finalMessage); + } else { + return finalMessage.toString(); + } + } +} diff --git a/apps/wyatt_clean_code/lib/data/data_sources/local/.gitkeep b/apps/wyatt_clean_code/lib/data/data_sources/local/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_clean_code/lib/data/data_sources/local/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_clean_code/lib/data/data_sources/remote/.gitkeep b/apps/wyatt_clean_code/lib/data/data_sources/remote/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_clean_code/lib/data/data_sources/remote/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_clean_code/lib/data/models/.gitkeep b/apps/wyatt_clean_code/lib/data/models/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_clean_code/lib/data/models/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_clean_code/lib/data/repositories/counter_repository_impl.dart b/apps/wyatt_clean_code/lib/data/repositories/counter_repository_impl.dart new file mode 100644 index 0000000..b255175 --- /dev/null +++ b/apps/wyatt_clean_code/lib/data/repositories/counter_repository_impl.dart @@ -0,0 +1,24 @@ +import 'package:wyatt_clean_code/core/enums/exception_type.dart'; +import 'package:wyatt_clean_code/core/errors/exceptions.dart'; +import 'package:wyatt_clean_code/domain/repositories/counter_repository.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class CounterRepositoryImpl implements CounterRepository { + Result _check(int value) => + Result.conditionalLazy( + value >= 0, + () => value, + () => ClientException( + AppExceptionType.assertion, + "Counter can't be negative!", + ), + ); + + @override + Future> decrement(int newState) async => + _check(newState); + + @override + Future> increment(int newState) async => + _check(newState); +} diff --git a/apps/wyatt_clean_code/lib/domain/data_sources/base_data_source.dart b/apps/wyatt_clean_code/lib/domain/data_sources/base_data_source.dart new file mode 100644 index 0000000..05f1202 --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/data_sources/base_data_source.dart @@ -0,0 +1 @@ +abstract class BaseDataSource {} diff --git a/apps/wyatt_clean_code/lib/domain/data_sources/local/base_local_data_source.dart b/apps/wyatt_clean_code/lib/domain/data_sources/local/base_local_data_source.dart new file mode 100644 index 0000000..175c70b --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/data_sources/local/base_local_data_source.dart @@ -0,0 +1,3 @@ +import 'package:wyatt_clean_code/domain/data_sources/base_data_source.dart'; + +abstract class BaseLocalDataSource extends BaseDataSource {} diff --git a/apps/wyatt_clean_code/lib/domain/data_sources/remote/base_remote_data_source.dart b/apps/wyatt_clean_code/lib/domain/data_sources/remote/base_remote_data_source.dart new file mode 100644 index 0000000..ef4acf0 --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/data_sources/remote/base_remote_data_source.dart @@ -0,0 +1,3 @@ +import 'package:wyatt_clean_code/domain/data_sources/base_data_source.dart'; + +abstract class BaseRemoteDataSource extends BaseDataSource {} diff --git a/apps/wyatt_clean_code/lib/domain/entities/.gitkeep b/apps/wyatt_clean_code/lib/domain/entities/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/entities/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_clean_code/lib/domain/repositories/base_repository.dart b/apps/wyatt_clean_code/lib/domain/repositories/base_repository.dart new file mode 100644 index 0000000..2b88f48 --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/repositories/base_repository.dart @@ -0,0 +1 @@ +abstract class BaseRepository {} diff --git a/apps/wyatt_clean_code/lib/domain/repositories/counter_repository.dart b/apps/wyatt_clean_code/lib/domain/repositories/counter_repository.dart new file mode 100644 index 0000000..171e77f --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/repositories/counter_repository.dart @@ -0,0 +1,8 @@ +import 'package:wyatt_clean_code/core/errors/exceptions.dart'; +import 'package:wyatt_clean_code/domain/repositories/base_repository.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +abstract class CounterRepository extends BaseRepository { + Future> increment(int newState); + Future> decrement(int newState); +} diff --git a/apps/wyatt_clean_code/lib/domain/usecases/counter/decrement_counter.dart b/apps/wyatt_clean_code/lib/domain/usecases/counter/decrement_counter.dart new file mode 100644 index 0000000..b1034b9 --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/usecases/counter/decrement_counter.dart @@ -0,0 +1,16 @@ +import 'package:wyatt_clean_code/core/errors/exceptions.dart'; +import 'package:wyatt_clean_code/core/usecases/usecase.dart'; +import 'package:wyatt_clean_code/domain/repositories/counter_repository.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class DecrementCounter extends UseCase { + final CounterRepository counterRepository; + + DecrementCounter({ + required this.counterRepository, + }); + + @override + Future> call(int params) async => + counterRepository.decrement(params); +} diff --git a/apps/wyatt_clean_code/lib/domain/usecases/counter/increment_counter.dart b/apps/wyatt_clean_code/lib/domain/usecases/counter/increment_counter.dart new file mode 100644 index 0000000..fd98d08 --- /dev/null +++ b/apps/wyatt_clean_code/lib/domain/usecases/counter/increment_counter.dart @@ -0,0 +1,16 @@ +import 'package:wyatt_clean_code/core/errors/exceptions.dart'; +import 'package:wyatt_clean_code/core/usecases/usecase.dart'; +import 'package:wyatt_clean_code/domain/repositories/counter_repository.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class IncrementCounter extends UseCase { + final CounterRepository counterRepository; + + IncrementCounter({ + required this.counterRepository, + }); + + @override + Future> call(int params) async => + counterRepository.increment(params); +} diff --git a/apps/wyatt_clean_code/lib/gen/colors.gen.dart b/apps/wyatt_clean_code/lib/gen/colors.gen.dart new file mode 100644 index 0000000..5693c5c --- /dev/null +++ b/apps/wyatt_clean_code/lib/gen/colors.gen.dart @@ -0,0 +1,195 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import + +import 'package:flutter/painting.dart'; +import 'package:flutter/material.dart'; + +class ColorName { + ColorName._(); + + /// Color: #FF1B1B1B + static const Color darkBackground = Color(0xFF1B1B1B); + + /// Color: #FFFFB4A9 + static const Color darkError = Color(0xFFFFB4A9); + + /// Color: #FF930006 + static const Color darkErrorContainer = Color(0xFF930006); + + /// Color: #FF0061A6 + static const Color darkInversePrimary = Color(0xFF0061A6); + + /// Color: #FFE2E2E6 + static const Color darkInverseSurface = Color(0xFFE2E2E6); + + /// Color: #FFE2E2E6 + static const Color darkOnBackground = Color(0xFFE2E2E6); + + /// Color: #FF680003 + static const Color darkOnError = Color(0xFF680003); + + /// Color: #FFFFB4A9 + static const Color darkOnErrorContainer = Color(0xFFFFB4A9); + + /// Color: #FF2F3033 + static const Color darkOnInverseSurface = Color(0xFF2F3033); + + /// Color: #FF00325A + static const Color darkOnPrimary = Color(0xFF00325A); + + /// Color: #FFD0E4FF + static const Color darkOnPrimaryContainer = Color(0xFFD0E4FF); + + /// Color: #FF253140 + static const Color darkOnSecondary = Color(0xFF253140); + + /// Color: #FFD6E3F7 + static const Color darkOnSecondaryContainer = Color(0xFFD6E3F7); + + /// Color: #FFE2E2E6 + static const Color darkOnSurface = Color(0xFFE2E2E6); + + /// Color: #FFC3C7D0 + static const Color darkOnSurfaceVariant = Color(0xFFC3C7D0); + + /// Color: #FF8D9199 + static const Color darkOutline = Color(0xFF8D9199); + + /// Color: #FF9CCAFF + static const Color darkPrimary = Color(0xFF9CCAFF); + + /// Color: #FF00497F + static const Color darkPrimaryContainer = Color(0xFF00497F); + + /// Color: #FFBBC8DB + static const Color darkSecondary = Color(0xFFBBC8DB); + + /// Color: #FF3C4858 + static const Color darkSecondaryContainer = Color(0xFF3C4858); + + /// Color: #FF000000 + static const Color darkShadow = Color(0xFF000000); + + /// Color: #FF1B1B1B + static const Color darkSurface = Color(0xFF1B1B1B); + + /// Color: #FF42474E + static const Color darkSurfaceVariant = Color(0xFF42474E); + + /// Color: #FFFDFCFF + static const Color lightBackground = Color(0xFFFDFCFF); + + /// Color: #FFBA1B1B + static const Color lightError = Color(0xFFBA1B1B); + + /// Color: #FFFFDAD4 + static const Color lightErrorContainer = Color(0xFFFFDAD4); + + /// Color: #FF9CCAFF + static const Color lightInversePrimary = Color(0xFF9CCAFF); + + /// Color: #FF2F3033 + static const Color lightInverseSurface = Color(0xFF2F3033); + + /// Color: #FF1B1B1B + static const Color lightOnBackground = Color(0xFF1B1B1B); + + /// Color: #FFFFFFFF + static const Color lightOnError = Color(0xFFFFFFFF); + + /// Color: #FF410001 + static const Color lightOnErrorContainer = Color(0xFF410001); + + /// Color: #FFF1F0F4 + static const Color lightOnInverseSurface = Color(0xFFF1F0F4); + + /// Color: #FFFFFFFF + static const Color lightOnPrimary = Color(0xFFFFFFFF); + + /// Color: #FF001D36 + static const Color lightOnPrimaryContainer = Color(0xFF001D36); + + /// Color: #FFFFFFFF + static const Color lightOnSecondary = Color(0xFFFFFFFF); + + /// Color: #FF101C2B + static const Color lightOnSecondaryContainer = Color(0xFF101C2B); + + /// Color: #FF1B1B1B + static const Color lightOnSurface = Color(0xFF1B1B1B); + + /// Color: #FF42474E + static const Color lightOnSurfaceVariant = Color(0xFF42474E); + + /// Color: #FF73777F + static const Color lightOutline = Color(0xFF73777F); + + /// Color: #FF0061A6 + static const Color lightPrimary = Color(0xFF0061A6); + + /// Color: #FFD0E4FF + static const Color lightPrimaryContainer = Color(0xFFD0E4FF); + + /// Color: #FF535F70 + static const Color lightSecondary = Color(0xFF535F70); + + /// Color: #FFD6E3F7 + static const Color lightSecondaryContainer = Color(0xFFD6E3F7); + + /// Color: #FF000000 + static const Color lightShadow = Color(0xFF000000); + + /// Color: #FFFDFCFF + static const Color lightSurface = Color(0xFFFDFCFF); + + /// Color: #FFDFE2EB + static const Color lightSurfaceVariant = Color(0xFFDFE2EB); + + /// MaterialColor: + /// 50: #FFFFE412FE + /// 100: #FFFFBC2DFB + /// 200: #FFFF904BF9 + /// 300: #FFFF6469F7 + /// 400: #FFFF428075 + /// 500: #FFFF2196F3 + /// 600: #FFFF1DC2114 + /// 700: #FFFF181B382C + /// 800: #FFFF14296C06 + /// 900: #FFFF0B432A01 + static const MaterialColor seedColor = MaterialColor( + 0xFFFF2196F3, + { + 50: Color(0xFFFFE412FE), + 100: Color(0xFFFFBC2DFB), + 200: Color(0xFFFF904BF9), + 300: Color(0xFFFF6469F7), + 400: Color(0xFFFF428075), + 500: Color(0xFFFF2196F3), + 600: Color(0xFFFF1DC2114), + 700: Color(0xFFFF181B382C), + 800: Color(0xFFFF14296C06), + 900: Color(0xFFFF0B432A01), + }, + ); + + /// MaterialAccentColor: + /// 100: #FFFFFFFF + /// 200: #FFFFFFFF + /// 400: #FFFFFFFF + /// 700: #FFFFFFFF + static const MaterialAccentColor seedColorAccent = MaterialAccentColor( + 0xFFFFFFFF, + { + 100: Color(0xFFFFFFFF), + 200: Color(0xFFFFFFFF), + 400: Color(0xFFFFFFFF), + 700: Color(0xFFFFFFFF), + }, + ); +} diff --git a/apps/wyatt_clean_code/lib/main.dart b/apps/wyatt_clean_code/lib/main.dart new file mode 100644 index 0000000..202509b --- /dev/null +++ b/apps/wyatt_clean_code/lib/main.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/apps/wyatt_clean_code/lib/main_development.dart b/apps/wyatt_clean_code/lib/main_development.dart new file mode 100644 index 0000000..8567ad1 --- /dev/null +++ b/apps/wyatt_clean_code/lib/main_development.dart @@ -0,0 +1,8 @@ +import 'package:wyatt_clean_code/bootstrap.dart'; +import 'package:wyatt_clean_code/core/flavors/flavor_settings.dart'; +import 'package:wyatt_clean_code/presentation/features/app/app.dart'; + +void main(List args) { + FlavorSettings.development(); + bootstrap(App.new); +} diff --git a/apps/wyatt_clean_code/lib/main_production.dart b/apps/wyatt_clean_code/lib/main_production.dart new file mode 100644 index 0000000..f863311 --- /dev/null +++ b/apps/wyatt_clean_code/lib/main_production.dart @@ -0,0 +1,8 @@ +import 'package:wyatt_clean_code/bootstrap.dart'; +import 'package:wyatt_clean_code/core/flavors/flavor_settings.dart'; +import 'package:wyatt_clean_code/presentation/features/app/app.dart'; + +void main(List args) { + FlavorSettings.production(); + bootstrap(App.new); +} diff --git a/apps/wyatt_clean_code/lib/main_staging.dart b/apps/wyatt_clean_code/lib/main_staging.dart new file mode 100644 index 0000000..d8c2eba --- /dev/null +++ b/apps/wyatt_clean_code/lib/main_staging.dart @@ -0,0 +1,8 @@ +import 'package:wyatt_clean_code/bootstrap.dart'; +import 'package:wyatt_clean_code/core/flavors/flavor_settings.dart'; +import 'package:wyatt_clean_code/presentation/features/app/app.dart'; + +void main(List args) { + FlavorSettings.staging(); + bootstrap(App.new); +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/app/app.dart b/apps/wyatt_clean_code/lib/presentation/features/app/app.dart new file mode 100644 index 0000000..bc4e4d2 --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/app/app.dart @@ -0,0 +1,71 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:wyatt_clean_code/core/design_system/theme.dart'; +import 'package:wyatt_clean_code/core/extensions/build_context_extension.dart'; +import 'package:wyatt_clean_code/core/flavors/flavor_settings.dart'; +import 'package:wyatt_clean_code/core/routes/router.dart'; +import 'package:wyatt_clean_code/core/utils/screen_util.dart'; +import 'package:wyatt_clean_code/data/repositories/counter_repository_impl.dart'; +import 'package:wyatt_clean_code/domain/repositories/counter_repository.dart'; + +class App extends StatelessWidget { + App({super.key}); + + final GoRouter _router = GoRouter( + initialLocation: '/', + routes: AppRouter.routes, + debugLogDiagnostics: true, + errorBuilder: (_, __) => const ColoredBox( + color: Colors.red, + ), + ); + + Widget _bannerFlavor(Widget child) { + final flavorInstance = FlavorSettings.get(); + if (flavorInstance.displayBanner && !kReleaseMode) { + return Directionality( + textDirection: TextDirection.ltr, + child: Banner( + location: BannerLocation.topEnd, + message: flavorInstance.flavor.short, + color: flavorInstance.flavor.color, + child: child, + ), + ); + } + return child; + } + + @override + Widget build(BuildContext context) { + ScreenUtil.init(); + return MultiRepositoryProvider( + providers: [ + RepositoryProvider( + lazy: true, + create: (context) => CounterRepositoryImpl(), + ), + ], + child: _bannerFlavor( + MaterialApp.router( + title: 'Wyatt Demo', + theme: AppTheme.light, + debugShowCheckedModeBanner: false, + routerDelegate: _router.routerDelegate, + routeInformationParser: _router.routeInformationParser, + routeInformationProvider: _router.routeInformationProvider, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + ), + ), + ); + } +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/counter/blocs/counter_cubit.dart b/apps/wyatt_clean_code/lib/presentation/features/counter/blocs/counter_cubit.dart new file mode 100644 index 0000000..105530f --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/counter/blocs/counter_cubit.dart @@ -0,0 +1,39 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wyatt_clean_code/domain/usecases/counter/decrement_counter.dart'; +import 'package:wyatt_clean_code/domain/usecases/counter/increment_counter.dart'; + +class CounterCubit extends Cubit { + final IncrementCounter _incrementCounter; + final DecrementCounter _decrementCounter; + + CounterCubit({ + required IncrementCounter incrementCounter, + required DecrementCounter decrementCounter, + }) : _incrementCounter = incrementCounter, + _decrementCounter = decrementCounter, + super(0); + + Future increment({int by = 1}) async { + // Use `.call(...)` to get documentation, but we can + // also directly use `(...)` + final response = await _incrementCounter.call(state + by); + emit( + response.fold( + (value) => value, + (error) => state, + ), + ); + } + + Future decrement({int by = 1}) async { + // Use `.call(...)` to get documentation, but we can + // also directly use `(...)` + final response = await _decrementCounter.call(state - by); + emit( + response.fold( + (value) => value, + (error) => state, + ), + ); + } +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/counter/counter_page.dart b/apps/wyatt_clean_code/lib/presentation/features/counter/counter_page.dart new file mode 100644 index 0000000..91a3d66 --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/counter/counter_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/state_management/counter_page_provider.dart'; + +class CounterPage extends StatelessWidget { + const CounterPage({super.key}); + + static const String pageName = 'counter'; + + @override + Widget build(BuildContext context) => const CounterPageProvider(); +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_page_provider.dart b/apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_page_provider.dart new file mode 100644 index 0000000..0a31844 --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_page_provider.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; +import 'package:wyatt_clean_code/domain/repositories/counter_repository.dart'; +import 'package:wyatt_clean_code/domain/usecases/counter/decrement_counter.dart'; +import 'package:wyatt_clean_code/domain/usecases/counter/increment_counter.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/blocs/counter_cubit.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/state_management/counter_text_consumer.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/widgets/counter_base.dart'; + +class CounterPageProvider extends CubitProviderScreen { + const CounterPageProvider({super.key}); + + @override + CounterCubit create(BuildContext context) => CounterCubit( + decrementCounter: DecrementCounter( + counterRepository: repo(context), + ), + incrementCounter: IncrementCounter( + counterRepository: repo(context), + ), + ); + + @override + Widget builder(BuildContext context) => CounterBase( + fabIncrement: () => bloc(context).increment(), + fabIncrementBy10: () => bloc(context).increment(by: 10), + fabDecrement: () => bloc(context).decrement(), + fabDecrementBy10: () => bloc(context).decrement(by: 10), + child: const CounterTextConsumer(), + ); +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_text_consumer.dart b/apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_text_consumer.dart new file mode 100644 index 0000000..614916f --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/counter/state_management/counter_text_consumer.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/blocs/counter_cubit.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/widgets/counter_text.dart'; + +class CounterTextConsumer extends CubitConsumerScreen { + const CounterTextConsumer({super.key}); + + @override + Widget onBuild(BuildContext context, int state) => CounterText(count: state); +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_base.dart b/apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_base.dart new file mode 100644 index 0000000..dac58f1 --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_base.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_clean_code/core/extensions/build_context_extension.dart'; +import 'package:wyatt_clean_code/presentation/shared/layouts/app_default_scaffold.dart'; + +class CounterBase extends StatelessWidget { + const CounterBase({ + required this.child, + this.fabIncrement, + this.fabIncrementBy10, + this.fabDecrement, + this.fabDecrementBy10, + super.key, + }); + + final void Function()? fabIncrement; + final void Function()? fabIncrementBy10; + final void Function()? fabDecrement; + final void Function()? fabDecrementBy10; + + final Widget child; + + @override + Widget build(BuildContext context) => AppDefaultScaffold( + title: Text(context.l10n.counterAppBarTitle), + body: Center( + child: child, + ), + fabChildren: [ + FloatingActionButton( + heroTag: 'increment_tag', + onPressed: fabIncrement, + child: const Icon(Icons.add), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'increment_10_tag', + onPressed: fabIncrementBy10, + child: const Text('+10'), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'decrement_tag', + onPressed: fabDecrement, + child: const Icon(Icons.remove), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'decrement_10_tag', + onPressed: fabDecrementBy10, + child: const Text('-10'), + ), + ], + ); +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_text.dart b/apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_text.dart new file mode 100644 index 0000000..29c0b9d --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/counter/widgets/counter_text.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_clean_code/core/extensions/build_context_extension.dart'; + +class CounterText extends StatelessWidget { + const CounterText({ + required this.count, + super.key, + }); + + final int count; + + @override + Widget build(BuildContext context) => Text( + context.l10n.youHavePushed(count), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline3, + ); +} diff --git a/apps/wyatt_clean_code/lib/presentation/features/initial/initial_page.dart b/apps/wyatt_clean_code/lib/presentation/features/initial/initial_page.dart new file mode 100644 index 0000000..8e3bd9b --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/features/initial/initial_page.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:wyatt_clean_code/core/extensions/build_context_extension.dart'; +import 'package:wyatt_clean_code/presentation/features/counter/counter_page.dart'; +import 'package:wyatt_clean_code/presentation/shared/layouts/app_default_scaffold.dart'; + +class InitialPage extends StatelessWidget { + const InitialPage({super.key}); + + static const String pageName = 'initial'; + + @override + Widget build(BuildContext context) => AppDefaultScaffold( + title: const Text('Wyatt Demo'), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + child: Text(context.l10n.goToCounter), + onPressed: () => context.pushNamed(CounterPage.pageName), + ), + ], + ), + ), + ); +} diff --git a/apps/wyatt_clean_code/lib/presentation/shared/layouts/app_default_scaffold.dart b/apps/wyatt_clean_code/lib/presentation/shared/layouts/app_default_scaffold.dart new file mode 100644 index 0000000..1d21aa1 --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/shared/layouts/app_default_scaffold.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class AppDefaultScaffold extends StatelessWidget { + const AppDefaultScaffold({ + required this.body, + this.title, + this.fabChildren, + super.key, + }); + + final Widget? title; + final Widget body; + final List? fabChildren; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: title), + body: body, + floatingActionButton: (fabChildren?.isNotEmpty ?? false) + ? Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: fabChildren!, + ) + : null, + ); +} diff --git a/apps/wyatt_clean_code/lib/presentation/shared/state_management/.gitkeep b/apps/wyatt_clean_code/lib/presentation/shared/state_management/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_clean_code/lib/presentation/shared/state_management/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_clean_code/pubspec.yaml b/apps/wyatt_clean_code/pubspec.yaml new file mode 100644 index 0000000..a881a54 --- /dev/null +++ b/apps/wyatt_clean_code/pubspec.yaml @@ -0,0 +1,126 @@ +name: wyatt_clean_code +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" + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: ^0.17.0 + go_router: ^4.1.1 + equatable: ^2.0.3 + freezed_annotation: ^2.1.0 + json_annotation: ^4.6.0 + cupertino_icons: ^1.0.5 + get_it: ^7.2.0 + logger: ^1.1.0 + gap: ^2.0.0 + flutter_bloc: ^8.0.1 + wyatt_bloc_helper: + git: + url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages + ref: bloc/feature/fix_and_repo + path: packages/wyatt_bloc_helper + wyatt_type_utils: + git: + url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages + ref: wyatt_type_utils-v0.0.2 + path: packages/wyatt_type_utils + + +dev_dependencies: + flutter_test: + sdk: flutter + + dependency_validator: ^3.2.2 + + build_runner: ^2.2.0 + flutter_gen_runner: ^4.3.0 + freezed: ^2.1.0+1 + json_serializable: ^6.3.1 + + # The "wyatt_analysis" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + wyatt_analysis: + git: + url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages + ref: wyatt_analysis-v2.2.1 + path: packages/wyatt_analysis + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following secion is specific to FlutterGen +flutter_gen: + colors: + inputs: + - assets/colors.xml + +# The following section is specific to Flutter packages. +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 + + generate: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # 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 diff --git a/apps/wyatt_clean_code/test/widget_test.dart b/apps/wyatt_clean_code/test/widget_test.dart new file mode 100644 index 0000000..fc26b28 --- /dev/null +++ b/apps/wyatt_clean_code/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:wyatt_clean_code/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}