feat: add apps folder

This commit is contained in:
Hugo Pointcheval 2022-07-21 17:35:22 +02:00
parent 97b70d065a
commit 3a79bcde7c
Signed by: hugo
GPG Key ID: A9E8E9615379254F
79 changed files with 2643 additions and 0 deletions

47
apps/wyatt_clean_code/.gitignore vendored Normal file
View File

@ -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

View File

@ -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'

View File

@ -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"
},
]
}

View File

@ -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,
}
],
}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.wyatt_clean_code">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.wyatt_clean_code">
<application
android:label="wyatt_clean_code"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,6 @@
package com.example.wyatt_clean_code
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.wyatt_clean_code">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -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
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="seedColor" type="material material-accent">#FF2196F3</color>
<color name="lightPrimary">#FF0061A6</color>
<color name="lightOnPrimary">#FFFFFFFF</color>
<color name="lightPrimaryContainer">#FFD0E4FF</color>
<color name="lightOnPrimaryContainer">#FF001D36</color>
<color name="lightSecondary">#FF535F70</color>
<color name="lightOnSecondary">#FFFFFFFF</color>
<color name="lightSecondaryContainer">#FFD6E3F7</color>
<color name="lightOnSecondaryContainer">#FF101C2B</color>
<color name="lightError">#FFBA1B1B</color>
<color name="lightOnError">#FFFFFFFF</color>
<color name="lightErrorContainer">#FFFFDAD4</color>
<color name="lightOnErrorContainer">#FF410001</color>
<color name="lightBackground">#FFFDFCFF</color>
<color name="lightOnBackground">#FF1B1B1B</color>
<color name="lightSurface">#FFFDFCFF</color>
<color name="lightOnSurface">#FF1B1B1B</color>
<color name="lightSurfaceVariant">#FFDFE2EB</color>
<color name="lightOnSurfaceVariant">#FF42474E</color>
<color name="lightOutline">#FF73777F</color>
<color name="lightShadow">#FF000000</color>
<color name="lightInverseSurface">#FF2F3033</color>
<color name="lightOnInverseSurface">#FFF1F0F4</color>
<color name="lightInversePrimary">#FF9CCAFF</color>
<color name="darkPrimary">#FF9CCAFF</color>
<color name="darkOnPrimary">#FF00325A</color>
<color name="darkPrimaryContainer">#FF00497F</color>
<color name="darkOnPrimaryContainer">#FFD0E4FF</color>
<color name="darkSecondary">#FFBBC8DB</color>
<color name="darkOnSecondary">#FF253140</color>
<color name="darkSecondaryContainer">#FF3C4858</color>
<color name="darkOnSecondaryContainer">#FFD6E3F7</color>
<color name="darkError">#FFFFB4A9</color>
<color name="darkOnError">#FF680003</color>
<color name="darkErrorContainer">#FF930006</color>
<color name="darkOnErrorContainer">#FFFFB4A9</color>
<color name="darkBackground">#FF1B1B1B</color>
<color name="darkOnBackground">#FFE2E2E6</color>
<color name="darkSurface">#FF1B1B1B</color>
<color name="darkOnSurface">#FFE2E2E6</color>
<color name="darkSurfaceVariant">#FF42474E</color>
<color name="darkOnSurfaceVariant">#FFC3C7D0</color>
<color name="darkOutline">#FF8D9199</color>
<color name="darkShadow">#FF000000</color>
<color name="darkInverseSurface">#FFE2E2E6</color>
<color name="darkOnInverseSurface">#FF2F3033</color>
<color name="darkInversePrimary">#FF0061A6</color>
</resources>

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
arb-dir: assets/l10n
template-arb-file: intl_fr.arb
output-localization-file: app_localizations.dart
nullable-getter: false

View File

@ -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<void> bootstrap(FutureOr<Widget> 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,
),
);
}

View File

@ -0,0 +1 @@
# just to keep empty folder in brick generation

View File

@ -0,0 +1,15 @@
import 'dart:async';
import 'package:get_it/get_it.dart';
final getIt = GetIt.I;
abstract class GetItInitializer {
static Future<void> init() async {
// Here, register data sources
}
static void run() {
unawaited(init());
}
}

View File

@ -0,0 +1,2 @@
/// Generate colors with `flutter pub run build_runner build`
export 'package:wyatt_clean_code/gen/colors.gen.dart';

View File

@ -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);
}

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -0,0 +1,7 @@
enum AppExceptionType {
network,
api,
database,
cache,
assertion,
}

View File

@ -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);
}

View File

@ -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<Object?> 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()}';
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,6 @@
import 'package:wyatt_clean_code/domain/data_sources/local/base_local_data_source.dart';
mixin LocalDataSource<Local extends BaseLocalDataSource> {
/// Offline data source, for debug or cache
Local get localDataSource;
}

View File

@ -0,0 +1,6 @@
import 'package:wyatt_clean_code/domain/data_sources/remote/base_remote_data_source.dart';
mixin RemoteDataSource<Remote extends BaseRemoteDataSource> {
/// Online data source, to provide data through API
Remote get remoteDataSource;
}

View File

@ -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<void> defaultTransition(
BuildContext context,
GoRouterState state,
Widget child,
) =>
MaterialPage<void>(
key: state.pageKey,
child: child,
);
static final List<GoRoute> 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(),
),
),
];
}

View File

@ -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<Type, Params> {
Future<Result<Type, AppException>> call(Params params);
}
class NoParams extends Equatable {
@override
List<Object> get props => [];
}

View File

@ -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<dynamic, dynamic> bloc, Object? event) {
super.onEvent(bloc, event);
if (printEvent) {
event?.d(wrap: (obj) => 'onEvent $event');
}
}
@override
void onError(BlocBase<dynamic> 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<dynamic> bloc, Change<dynamic> change) {
super.onChange(bloc, change);
if (printChange) {
change.d(wrap: (obj) => 'onChange(${bloc.runtimeType}, $obj)');
}
}
@override
void onTransition(
Bloc<dynamic, dynamic> bloc,
Transition<dynamic, dynamic> transition,
) {
super.onTransition(bloc, transition);
if (printTransition) {
transition.d(wrap: (obj) => 'onTransition $obj');
}
}
}

View File

@ -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));
}

View File

@ -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<String> 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();
}
}
}

View File

@ -0,0 +1 @@
# just to keep empty folder in brick generation

View File

@ -0,0 +1 @@
# just to keep empty folder in brick generation

View File

@ -0,0 +1 @@
# just to keep empty folder in brick generation

View File

@ -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<int, AppException> _check(int value) =>
Result.conditionalLazy<int, AppException>(
value >= 0,
() => value,
() => ClientException(
AppExceptionType.assertion,
"Counter can't be negative!",
),
);
@override
Future<Result<int, AppException>> decrement(int newState) async =>
_check(newState);
@override
Future<Result<int, AppException>> increment(int newState) async =>
_check(newState);
}

View File

@ -0,0 +1 @@
abstract class BaseDataSource {}

View File

@ -0,0 +1,3 @@
import 'package:wyatt_clean_code/domain/data_sources/base_data_source.dart';
abstract class BaseLocalDataSource extends BaseDataSource {}

View File

@ -0,0 +1,3 @@
import 'package:wyatt_clean_code/domain/data_sources/base_data_source.dart';
abstract class BaseRemoteDataSource extends BaseDataSource {}

View File

@ -0,0 +1 @@
# just to keep empty folder in brick generation

View File

@ -0,0 +1 @@
abstract class BaseRepository {}

View File

@ -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<Result<int, AppException>> increment(int newState);
Future<Result<int, AppException>> decrement(int newState);
}

View File

@ -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<int, int> {
final CounterRepository counterRepository;
DecrementCounter({
required this.counterRepository,
});
@override
Future<Result<int, AppException>> call(int params) async =>
counterRepository.decrement(params);
}

View File

@ -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<int, int> {
final CounterRepository counterRepository;
IncrementCounter({
required this.counterRepository,
});
@override
Future<Result<int, AppException>> call(int params) async =>
counterRepository.increment(params);
}

View File

@ -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,
<int, Color>{
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,
<int, Color>{
100: Color(0xFFFFFFFF),
200: Color(0xFFFFFFFF),
400: Color(0xFFFFFFFF),
700: Color(0xFFFFFFFF),
},
);
}

View File

@ -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<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
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: <Widget>[
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.
);
}
}

View File

@ -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<String> args) {
FlavorSettings.development();
bootstrap(App.new);
}

View File

@ -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<String> args) {
FlavorSettings.production();
bootstrap(App.new);
}

View File

@ -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<String> args) {
FlavorSettings.staging();
bootstrap(App.new);
}

View File

@ -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<CounterRepository>(
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,
),
),
);
}
}

View File

@ -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<int> {
final IncrementCounter _incrementCounter;
final DecrementCounter _decrementCounter;
CounterCubit({
required IncrementCounter incrementCounter,
required DecrementCounter decrementCounter,
}) : _incrementCounter = incrementCounter,
_decrementCounter = decrementCounter,
super(0);
Future<void> 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<void> 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,
),
);
}
}

View File

@ -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();
}

View File

@ -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<CounterCubit, int> {
const CounterPageProvider({super.key});
@override
CounterCubit create(BuildContext context) => CounterCubit(
decrementCounter: DecrementCounter(
counterRepository: repo<CounterRepository>(context),
),
incrementCounter: IncrementCounter(
counterRepository: repo<CounterRepository>(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(),
);
}

View File

@ -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<CounterCubit, int> {
const CounterTextConsumer({super.key});
@override
Widget onBuild(BuildContext context, int state) => CounterText(count: state);
}

View File

@ -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'),
),
],
);
}

View File

@ -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,
);
}

View File

@ -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),
),
],
),
),
);
}

View File

@ -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<Widget>? 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,
);
}

View File

@ -0,0 +1 @@
# just to keep empty folder in brick generation

View File

@ -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

View File

@ -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);
});
}