diff --git a/apps/wyatt_app_template/brick_config.yaml b/apps/wyatt_app_template/brick_config.yaml index b90fd25..840bb2a 100644 --- a/apps/wyatt_app_template/brick_config.yaml +++ b/apps/wyatt_app_template/brick_config.yaml @@ -11,18 +11,21 @@ vars: description: The display name default: Wyatt App prompt: "What is the display name?" + project_name: name: wyatt_app_template type: string description: The project name default: wyatt_app prompt: "What is the project name?" - org_name: + + bundle_id: name: io.wyattapp.new type: string - description: The organization name + description: The bundle id used in Android and iOS default: io.wyattapp.new - prompt: "What is the organization name?" + prompt: "What is the bundle id?" + description: name: wyatt_description type: string diff --git a/apps/wyatt_app_template/wyatt_app_template/.metadata b/apps/wyatt_app_template/wyatt_app_template/.metadata index ddd7794..32d8944 100644 --- a/apps/wyatt_app_template/wyatt_app_template/.metadata +++ b/apps/wyatt_app_template/wyatt_app_template/.metadata @@ -15,10 +15,7 @@ migration: - platform: root create_revision: 135454af32477f815a7525073027a3ff9eff1bfd base_revision: 135454af32477f815a7525073027a3ff9eff1bfd - - platform: android - create_revision: 135454af32477f815a7525073027a3ff9eff1bfd - base_revision: 135454af32477f815a7525073027a3ff9eff1bfd - - platform: ios + - platform: web create_revision: 135454af32477f815a7525073027a3ff9eff1bfd base_revision: 135454af32477f815a7525073027a3ff9eff1bfd diff --git a/apps/wyatt_app_template/wyatt_app_template/README.md b/apps/wyatt_app_template/wyatt_app_template/README.md index 610f8d2..1545a0b 100644 --- a/apps/wyatt_app_template/wyatt_app_template/README.md +++ b/apps/wyatt_app_template/wyatt_app_template/README.md @@ -4,6 +4,56 @@ wyatt_description ## Requirements -- Flutter -- Taskfile -- Trapeze (with `npm install` thanks to `package.json`) \ No newline at end of file +* Flutter +* Taskfile +* Trapeze (with `npm install` thanks to `package.json`) + +### Configuration + +Create `.env` file with + +```sh +cp .env.example .env +``` + +### Taskfile + +Available tasks: + +| Command | Description | Aliases | +|----|-----|-----| +| `clean` | Cleans the environment.| `cl` | +| `format` |Formats the code.| `fmt` | +| `help` |Help dialog.| `h, default` | +| `lint` |Lints the code.| `l` | +| `start-emulators` | Start needed emulators.| `emu` | +| `build:android` | Building Android APK| `build:a` | +| `build:ios` | Building iOS IPA| `build:i` | +| `gen:build` | Running build runner| `gen:b` | +| `gen:build-delete` |Running build runner with deletion of conflicting outputs| `gen:d` | +| `gen:clean` | Cleaning build runner| `gen:c` | +| `gen:intl` |Generating internationalization file| `gen:i` | +| `gen:trapeze` | Running Trapeze config| `gen:t` | +| `gen:watch` | Running build runner in watch mode| `gen:w` | +| `pub:get` | Getting latest dependencies| `pub:g` | +| `pub:outdated` |Checking for outdated dependencies| `pub:o` | +| `pub:upgrade` | Upgrading dependencies| `pub:u` | +| `pub:upgrade-major` | Upgrading dependencies| `pub:um` | +| `pub:validate` |Running dependency validator| `pub:v` | +| `run:dev` | Run app in development environment| `run:d` | +| `run:emu` | Run app in development with emulated environment| `run:e` | +| `run:logs` |Show log output for running Flutter apps| `run:l` | +| `run:mock` |Run app in development environment with mocks| `run:m` | +| `run:prod` |Run app in production environment| `run:p` | +| `run:release` | Run app in production environment and in release mode| `run:r` | +| `run:staging` | Run app in staging environment| `run:s` | + +### Flavors + +| Flavor | Details | +|-------|--------| +| Development | Use `--dart-define="dev_mode="` to choose between `mock` , `emulator` and `real` | +| Staging | With a green banner. Only `real` mode available (remote data sources) | +| Production | Only `real` mode available (remote data sources) | + +> In `lib/core/flavors/flavor.dart` you can customize flavors. diff --git a/apps/wyatt_app_template/wyatt_app_template/analysis_options.yaml b/apps/wyatt_app_template/wyatt_app_template/analysis_options.yaml index f165353..6b75b4c 100644 --- a/apps/wyatt_app_template/wyatt_app_template/analysis_options.yaml +++ b/apps/wyatt_app_template/wyatt_app_template/analysis_options.yaml @@ -1,5 +1,9 @@ include: package:wyatt_analysis/analysis_options.flutter.yaml +analyzer: + plugins: + - dart_code_metrics + dart_code_metrics: anti-patterns: - long-method diff --git a/apps/wyatt_app_template/wyatt_app_template/assets/fonts/.gitkeep b/apps/wyatt_app_template/wyatt_app_template/assets/fonts/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/assets/fonts/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_app_template/wyatt_app_template/ios/Podfile.lock b/apps/wyatt_app_template/wyatt_app_template/ios/Podfile.lock new file mode 100644 index 0000000..a5acb8c --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/ios/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - Flutter (1.0.0) + - flutter_native_splash (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_native_splash: + :path: ".symlinks/plugins/flutter_native_splash/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.11.3 diff --git a/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcodeproj/project.pbxproj b/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcodeproj/project.pbxproj index f45320b..a1c40c5 100644 --- a/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcodeproj/project.pbxproj +++ b/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 50F1D9CF301DADBFF7F91098 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F58D6DFA5E148E9B1E5401F /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -29,8 +30,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 01240B090F6ECE02483CB484 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 18B4FB2B44E101F10AB8D41B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 1F58D6DFA5E148E9B1E5401F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -42,6 +46,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EA780011634A6BC46817CBE0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,32 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50F1D9CF301DADBFF7F91098 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 10872F84FB14C7DD915284C6 /* Pods */ = { + isa = PBXGroup; + children = ( + 18B4FB2B44E101F10AB8D41B /* Pods-Runner.debug.xcconfig */, + 01240B090F6ECE02483CB484 /* Pods-Runner.release.xcconfig */, + EA780011634A6BC46817CBE0 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 4204558B17A62367423769E8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1F58D6DFA5E148E9B1E5401F /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +97,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 10872F84FB14C7DD915284C6 /* Pods */, + 4204558B17A62367423769E8 /* Frameworks */, ); sourceTree = ""; }; @@ -105,12 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + B0C681F6443208846EBFB7D2 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + CF936C6216E6F00FB5F73F4C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -197,6 +226,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + B0C681F6443208846EBFB7D2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CF936C6216E6F00FB5F73F4C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcworkspace/contents.xcworkspacedata b/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/apps/wyatt_app_template/wyatt_app_template/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/bootstrap.dart b/apps/wyatt_app_template/wyatt_app_template/lib/bootstrap.dart index cf39bb3..fa13271 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/bootstrap.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/bootstrap.dart @@ -2,14 +2,13 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:wyatt_app_template/core/dependency_injection/get_it.dart'; import 'package:wyatt_app_template/core/flavors/flavor.dart'; import 'package:wyatt_app_template/core/utils/app_bloc_observer.dart'; Future bootstrap(FutureOr Function() builder) async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + // FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); Bloc.observer = AppBlocObserver(); diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/core/dependency_injection/get_it.dart b/apps/wyatt_app_template/wyatt_app_template/lib/core/dependency_injection/get_it.dart index 5a5b94d..fa93316 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/core/dependency_injection/get_it.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/core/dependency_injection/get_it.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:get_it/get_it.dart'; import 'package:wyatt_app_template/core/enums/dev_mode.dart'; import 'package:wyatt_app_template/core/flavors/flavor.dart'; +import 'package:wyatt_app_template/data/data_sources/local/counter_data_source_impl.dart'; +import 'package:wyatt_app_template/domain/data_sources/local/counter_data_source.dart'; final getIt = GetIt.I; @@ -10,10 +12,13 @@ final getIt = GetIt.I; abstract class GetItInitializer { static FutureOr _initCommon() async { // Initialize common sources/services + getIt.registerLazySingleton( + CounterDataSourceImpl.new, + ); } static FutureOr _initMocks() async { - // Initialize mocked sources/services + // Initialize mocked sources/services. } static FutureOr _initReal() async { diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/core/enums/dev_mode.dart b/apps/wyatt_app_template/wyatt_app_template/lib/core/enums/dev_mode.dart index ab67a28..bd4ad84 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/core/enums/dev_mode.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/core/enums/dev_mode.dart @@ -14,6 +14,7 @@ enum DevMode { return m; } } + return fallback; } } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/core/flavors/flavor.dart b/apps/wyatt_app_template/wyatt_app_template/lib/core/flavors/flavor.dart index 8c8657f..a4fd89e 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/core/flavors/flavor.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/core/flavors/flavor.dart @@ -1,8 +1,10 @@ +import 'package:flutter/material.dart'; import 'package:wyatt_app_template/core/enums/dev_mode.dart'; abstract class Flavor { Flavor._({ this.banner, + this.bannerColor = Colors.red, this.devMode, }) { _instance = this; @@ -11,6 +13,7 @@ abstract class Flavor { static Flavor? _instance; final String? banner; + final Color bannerColor; final DevMode? devMode; /// Returns [Flavor] instance. @@ -18,6 +21,7 @@ abstract class Flavor { if (_instance == null) { throw Exception('Flavor not initialized!'); } + return _instance!; } @@ -35,7 +39,7 @@ class DevelopmentFlavor extends Flavor { DevelopmentFlavor._({ required DevMode devMode, }) : super._( - banner: 'Mock', + banner: 'Dev', devMode: devMode, ); } @@ -44,6 +48,7 @@ class StagingFlavor extends Flavor { StagingFlavor() : super._( banner: 'Staging', + bannerColor: Colors.green, ); } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/core/routes/router.dart b/apps/wyatt_app_template/wyatt_app_template/lib/core/routes/router.dart index 79d65b2..c0a5220 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/core/routes/router.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/core/routes/router.dart @@ -1,5 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; +import 'package:wyatt_app_template/presentation/features/counter/counter.dart'; +import 'package:wyatt_app_template/presentation/features/home/home.dart'; abstract class AppRouter { /// Default transition for all pages @@ -26,7 +28,7 @@ abstract class AppRouter { ); /// Defines public routes (no authentication needed). - /// + /// /// Example: /// ```dart /// static final publicRoutes = [ @@ -38,5 +40,26 @@ abstract class AppRouter { static final List publicRoutes = []; /// Defines GoRoute routes. - static final List routes = []; + static final List routes = [ + GoRoute( + path: '/', + name: Home.pageName, + pageBuilder: (context, state) => + defaultTransition(context, state, const Home()), + ), + GoRoute( + path: '/counter', + name: Counter.pageName, + pageBuilder: (context, state) => + defaultTransition(context, state, const Counter()), + ), + ]; + + /// Router + static GoRouter router = GoRouter( + initialLocation: '/', + routes: AppRouter.routes, + debugLogDiagnostics: true, + redirect: (context, state) => null, + ); } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/core/utils/app_bloc_observer.dart b/apps/wyatt_app_template/wyatt_app_template/lib/core/utils/app_bloc_observer.dart index cb417f8..92be592 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/core/utils/app_bloc_observer.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/core/utils/app_bloc_observer.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -const _messageLength = 120; +const _messageLength = 100; class AppBlocObserver extends BlocObserver { AppBlocObserver({ @@ -20,6 +20,7 @@ class AppBlocObserver extends BlocObserver { String sanitize(Object? object) { final message = object.toString(); + return fullPrint ? message : message.substring( @@ -32,16 +33,17 @@ class AppBlocObserver extends BlocObserver { void onEvent(Bloc bloc, Object? event) { super.onEvent(bloc, event); if (printEvent) { - debugPrint('onEvent(${bloc.runtimeType}, ${sanitize(event)})'); + debugPrint('onEvent: ${bloc.runtimeType}\n' + '> event: ${sanitize(event)}'); } } @override void onError(BlocBase bloc, Object error, StackTrace stackTrace) { if (printError) { - debugPrint( - 'onError(${bloc.runtimeType}, ${sanitize(error)})\n$stackTrace', - ); + debugPrint('onError: ${bloc.runtimeType}\n' + '> error: ${sanitize(error)}\n' + '$stackTrace'); } super.onError(bloc, error, stackTrace); } @@ -50,7 +52,9 @@ class AppBlocObserver extends BlocObserver { void onChange(BlocBase bloc, Change change) { super.onChange(bloc, change); if (printChange) { - debugPrint('onChange(${bloc.runtimeType}, ${sanitize(change)})'); + debugPrint('onChange: ${bloc.runtimeType}\n' + '> currentState: ${sanitize(change.currentState)}\n' + '> nextState: ${sanitize(change.nextState)}'); } } @@ -61,7 +65,10 @@ class AppBlocObserver extends BlocObserver { ) { super.onTransition(bloc, transition); if (printTransition) { - debugPrint('onTransition(${bloc.runtimeType}, ${sanitize(transition)})'); + debugPrint('onTransition: ${bloc.runtimeType}\n' + '> currentState: ${sanitize(transition.currentState)}\n' + '> event: ${sanitize(transition.event)}\n' + '> nextState: ${sanitize(transition.nextState)}'); } } } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/data/data_sources/local/counter_data_source_impl.dart b/apps/wyatt_app_template/wyatt_app_template/lib/data/data_sources/local/counter_data_source_impl.dart new file mode 100644 index 0000000..2e48978 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/data/data_sources/local/counter_data_source_impl.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +import 'package:wyatt_app_template/data/models/integer_model.dart'; +import 'package:wyatt_app_template/domain/data_sources/local/counter_data_source.dart'; +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class CounterDataSourceImpl extends CounterDataSource { + // Simulate external data processing + String actual = '{"value": 0}'; + + @override + Future decrement(Integer value) async { + final current = + IntegerModel.fromJson(json.decode(actual) as Map); + + final newValue = current.value - value.value; + if (newValue < 0) { + throw ClientException("Counter can't be negative!"); + } + + final newInteger = IntegerModel(value: newValue); + actual = jsonEncode(newInteger.toJson()); + + return newInteger; + } + + @override + Future increment(Integer value) async { + final current = + IntegerModel.fromJson(json.decode(actual) as Map); + + final newValue = current.value + value.value; + if (newValue < 0) { + throw ClientException("Counter can't be negative!"); + } + + final newInteger = IntegerModel(value: newValue); + actual = jsonEncode(newInteger.toJson()); + + return newInteger; + } + + @override + Future getCurrent() async { + final current = + IntegerModel.fromJson(json.decode(actual) as Map); + + return current; + } + + @override + Future reset() async { + const newInteger = IntegerModel(value: 0); + actual = jsonEncode(newInteger.toJson()); + + return newInteger; + } +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/data/data_sources/remote/.gitkeep b/apps/wyatt_app_template/wyatt_app_template/lib/data/data_sources/remote/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/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_app_template/wyatt_app_template/lib/data/models/integer_model.dart b/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.dart new file mode 100644 index 0000000..53b7c98 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:wyatt_app_template/domain/entities/integer.dart'; + +part 'integer_model.freezed.dart'; +part 'integer_model.g.dart'; + +@freezed +class IntegerModel extends Integer with _$IntegerModel { + const factory IntegerModel({ + required int value, + }) = _IntegerModel; + + factory IntegerModel.fromJson(Map json) => + _$IntegerModelFromJson(json); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.freezed.dart b/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.freezed.dart new file mode 100644 index 0000000..bed9972 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.freezed.dart @@ -0,0 +1,151 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'integer_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +IntegerModel _$IntegerModelFromJson(Map json) { + return _IntegerModel.fromJson(json); +} + +/// @nodoc +mixin _$IntegerModel { + int get value => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IntegerModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IntegerModelCopyWith<$Res> { + factory $IntegerModelCopyWith( + IntegerModel value, $Res Function(IntegerModel) then) = + _$IntegerModelCopyWithImpl<$Res, IntegerModel>; + @useResult + $Res call({int value}); +} + +/// @nodoc +class _$IntegerModelCopyWithImpl<$Res, $Val extends IntegerModel> + implements $IntegerModelCopyWith<$Res> { + _$IntegerModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + }) { + return _then(_value.copyWith( + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_IntegerModelCopyWith<$Res> + implements $IntegerModelCopyWith<$Res> { + factory _$$_IntegerModelCopyWith( + _$_IntegerModel value, $Res Function(_$_IntegerModel) then) = + __$$_IntegerModelCopyWithImpl<$Res>; + @override + @useResult + $Res call({int value}); +} + +/// @nodoc +class __$$_IntegerModelCopyWithImpl<$Res> + extends _$IntegerModelCopyWithImpl<$Res, _$_IntegerModel> + implements _$$_IntegerModelCopyWith<$Res> { + __$$_IntegerModelCopyWithImpl( + _$_IntegerModel _value, $Res Function(_$_IntegerModel) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + }) { + return _then(_$_IntegerModel( + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$_IntegerModel implements _IntegerModel { + const _$_IntegerModel({required this.value}); + + factory _$_IntegerModel.fromJson(Map json) => + _$$_IntegerModelFromJson(json); + + @override + final int value; + + @override + String toString() { + return 'IntegerModel(value: $value)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_IntegerModel && + (identical(other.value, value) || other.value == value)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_IntegerModelCopyWith<_$_IntegerModel> get copyWith => + __$$_IntegerModelCopyWithImpl<_$_IntegerModel>(this, _$identity); + + @override + Map toJson() { + return _$$_IntegerModelToJson( + this, + ); + } +} + +abstract class _IntegerModel implements IntegerModel { + const factory _IntegerModel({required final int value}) = _$_IntegerModel; + + factory _IntegerModel.fromJson(Map json) = + _$_IntegerModel.fromJson; + + @override + int get value; + @override + @JsonKey(ignore: true) + _$$_IntegerModelCopyWith<_$_IntegerModel> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.g.dart b/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.g.dart new file mode 100644 index 0000000..291e5cf --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/data/models/integer_model.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'integer_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$_IntegerModel _$$_IntegerModelFromJson(Map json) => + _$_IntegerModel( + value: json['value'] as int, + ); + +Map _$$_IntegerModelToJson(_$_IntegerModel instance) => + { + 'value': instance.value, + }; diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/data/repositories/counter_repository_impl.dart b/apps/wyatt_app_template/wyatt_app_template/lib/data/repositories/counter_repository_impl.dart new file mode 100644 index 0000000..934d42a --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/data/repositories/counter_repository_impl.dart @@ -0,0 +1,41 @@ +import 'package:wyatt_app_template/domain/data_sources/local/counter_data_source.dart'; +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class CounterRepositoryImpl extends CounterRepository { + CounterRepositoryImpl({ + required CounterDataSource counterDataSource, + }) : _counterDataSource = counterDataSource; + + final CounterDataSource _counterDataSource; + + @override + FutureOrResult decrement({Integer by = const Integer(1)}) => + Result.tryCatchAsync( + () async => _counterDataSource.decrement(by), + (error) => error, + ); + + @override + FutureOrResult increment({Integer by = const Integer(1)}) => + Result.tryCatchAsync( + () async => _counterDataSource.increment(by), + (error) => error, + ); + + @override + FutureOrResult getCurrent() => + Result.tryCatchAsync( + () async => _counterDataSource.getCurrent(), + (error) => error, + ); + + @override + FutureOrResult reset() => + Result.tryCatchAsync( + () async => _counterDataSource.reset(), + (error) => error, + ); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/data_sources/local/counter_data_source.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/data_sources/local/counter_data_source.dart new file mode 100644 index 0000000..fabb91a --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/data_sources/local/counter_data_source.dart @@ -0,0 +1,9 @@ +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class CounterDataSource extends BaseDataSource { + Future decrement(Integer value); + Future increment(Integer value); + Future getCurrent(); + Future reset(); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/data_sources/remote/.gitkeep b/apps/wyatt_app_template/wyatt_app_template/lib/domain/data_sources/remote/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/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_app_template/wyatt_app_template/lib/domain/entities/integer.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/entities/integer.dart new file mode 100644 index 0000000..aea24f1 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/entities/integer.dart @@ -0,0 +1,11 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class Integer extends Entity { + const Integer(this.value); + + final int value; + + @override + String toString() => 'Integer(value: $value)'; +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/repositories/counter_repository.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/repositories/counter_repository.dart new file mode 100644 index 0000000..e3b7dd2 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/repositories/counter_repository.dart @@ -0,0 +1,9 @@ +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class CounterRepository extends BaseRepository { + FutureOrResult decrement({Integer by = const Integer(1)}); + FutureOrResult increment({Integer by = const Integer(1)}); + FutureOrResult getCurrent(); + FutureOrResult reset(); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/decrement.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/decrement.dart new file mode 100644 index 0000000..3825f48 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/decrement.dart @@ -0,0 +1,18 @@ +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class Decrement extends AsyncUseCase { + Decrement({ + required CounterRepository counterRepository, + }) : _counterRepository = counterRepository; + + final CounterRepository _counterRepository; + + @override + FutureOrResult execute(int? params) async { + final step = Integer(params ?? 1); + + return _counterRepository.decrement(by: step); + } +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/get_current.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/get_current.dart new file mode 100644 index 0000000..4f1f5cd --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/get_current.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2023 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class GetCurrent extends AsyncUseCase { + GetCurrent({ + required CounterRepository counterRepository, + }) : _counterRepository = counterRepository; + + final CounterRepository _counterRepository; + + @override + FutureOrResult execute(void params) async => + _counterRepository.getCurrent(); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/increment.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/increment.dart new file mode 100644 index 0000000..3eed0a2 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/increment.dart @@ -0,0 +1,18 @@ +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class Increment extends AsyncUseCase { + Increment({ + required CounterRepository counterRepository, + }) : _counterRepository = counterRepository; + + final CounterRepository _counterRepository; + + @override + FutureOrResult execute(int? params) async { + final step = Integer(params ?? 1); + + return _counterRepository.increment(by: step); + } +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/reset.dart b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/reset.dart new file mode 100644 index 0000000..ea1eb65 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/domain/usecases/counter/reset.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2023 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_app_template/domain/entities/integer.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class Reset extends AsyncUseCase { + Reset({ + required CounterRepository counterRepository, + }) : _counterRepository = counterRepository; + + final CounterRepository _counterRepository; + + @override + FutureOrResult execute(void params) async => + _counterRepository.reset(); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/gen/assets.gen.dart b/apps/wyatt_app_template/wyatt_app_template/lib/gen/assets.gen.dart new file mode 100644 index 0000000..3deea52 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/gen/assets.gen.dart @@ -0,0 +1,92 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal + +import 'package:flutter/widgets.dart'; + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// File path: assets/images/wyatt_logo.jpeg + AssetGenImage get wyattLogo => + const AssetGenImage('assets/images/wyatt_logo.jpeg'); + + /// List of all assets + List get values => [wyattLogo]; +} + +class Assets { + Assets._(); + + static const $AssetsImagesGen images = $AssetsImagesGen(); +} + +class AssetGenImage { + const AssetGenImage(this._assetName); + + final String _assetName; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.low, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider() => AssetImage(_assetName); + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/gen/colors.gen.dart b/apps/wyatt_app_template/wyatt_app_template/lib/gen/colors.gen.dart new file mode 100644 index 0000000..7140be2 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/gen/colors.gen.dart @@ -0,0 +1,21 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal + +import 'package:flutter/painting.dart'; +import 'package:flutter/material.dart'; + +class ColorName { + ColorName._(); + + /// Color: #000000 + static const Color black = Color(0xFF000000); + + /// Color: #FFFFFF + static const Color white = Color(0xFFFFFFFF); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/main_development.dart b/apps/wyatt_app_template/wyatt_app_template/lib/main_development.dart index 158d435..137498e 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/main_development.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/main_development.dart @@ -1,11 +1,11 @@ -import 'package:flutter/material.dart'; import 'package:wyatt_app_template/bootstrap.dart'; import 'package:wyatt_app_template/core/flavors/flavor.dart'; +import 'package:wyatt_app_template/presentation/features/app/app.dart'; void main(List args) { // Define environment DevelopmentFlavor(); // Initialize environment and variables - bootstrap(() async => Container()); + bootstrap(App.new); } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/main_production.dart b/apps/wyatt_app_template/wyatt_app_template/lib/main_production.dart index 69fdf89..525233f 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/main_production.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/main_production.dart @@ -1,11 +1,11 @@ -import 'package:flutter/material.dart'; import 'package:wyatt_app_template/bootstrap.dart'; import 'package:wyatt_app_template/core/flavors/flavor.dart'; +import 'package:wyatt_app_template/presentation/features/app/app.dart'; void main(List args) { // Define environment ProductionFlavor(); // Initialize environment and variables - bootstrap(() async => Container()); + bootstrap(App.new); } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/main_staging.dart b/apps/wyatt_app_template/wyatt_app_template/lib/main_staging.dart index 5d63b09..b489323 100644 --- a/apps/wyatt_app_template/wyatt_app_template/lib/main_staging.dart +++ b/apps/wyatt_app_template/wyatt_app_template/lib/main_staging.dart @@ -1,11 +1,11 @@ -import 'package:flutter/material.dart'; import 'package:wyatt_app_template/bootstrap.dart'; import 'package:wyatt_app_template/core/flavors/flavor.dart'; +import 'package:wyatt_app_template/presentation/features/app/app.dart'; void main(List args) { // Define environment StagingFlavor(); // Initialize environment and variables - bootstrap(() async => Container()); + bootstrap(App.new); } diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/app/app.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/app/app.dart new file mode 100644 index 0000000..7825f54 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/app/app.dart @@ -0,0 +1,63 @@ +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:wyatt_app_template/core/dependency_injection/get_it.dart'; +import 'package:wyatt_app_template/core/flavors/flavor.dart'; +import 'package:wyatt_app_template/core/routes/router.dart'; +import 'package:wyatt_app_template/data/repositories/counter_repository_impl.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_app_template/gen/app_localizations.dart'; +import 'package:wyatt_app_template/presentation/features/counter/blocs/counter_bloc/counter_bloc.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class App extends StatelessWidget { + const App({super.key}); + + Widget _flavorBanner(Widget child) { + final flavor = Flavor.get(); + if (flavor.banner != null && !kReleaseMode) { + return Directionality( + textDirection: TextDirection.ltr, + child: Banner( + location: BannerLocation.topEnd, + message: flavor.banner!, + color: flavor.bannerColor, + child: child, + ), + ); + } + + return child; + } + + @override + Widget build(BuildContext context) => MultiProvider( + repositoryProviders: [ + RepositoryProvider( + create: (_) => CounterRepositoryImpl(counterDataSource: getIt()), + ), + ], + blocProviders: [ + BlocProvider( + create: (_) => CounterBloc(), + ), + ], + child: _flavorBanner( + MaterialApp.router( + title: 'Wyatt App', + debugShowCheckedModeBanner: false, + routerDelegate: AppRouter.router.routerDelegate, + routeInformationParser: AppRouter.router.routeInformationParser, + routeInformationProvider: AppRouter.router.routeInformationProvider, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + ), + ), + ); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_bloc.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_bloc.dart new file mode 100644 index 0000000..cd0683d --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_bloc.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'counter_event.dart'; +part 'counter_state.dart'; + +/// {@template counter_bloc} +/// CounterBloc description +/// {@endtemplate} +class CounterBloc extends Bloc { + /// {@macro counter_bloc} + CounterBloc() : super(const CounterInitial()) { + on(_onCustomCounterEvent); + } + + FutureOr _onCustomCounterEvent( + CustomCounterEvent event, + Emitter emit, + ) async { + // TODO(wyatt): Add custom UI logic + const _ = 1 + 1; + + return; + } +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_event.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_event.dart new file mode 100644 index 0000000..49c34d2 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_event.dart @@ -0,0 +1,20 @@ +part of 'counter_bloc.dart'; + +/// {@template counter_event} +/// CounterEvent description +/// {@endtemplate} +abstract class CounterEvent extends Equatable { + /// {@macro counter_event} + const CounterEvent(); +} + +/// {@template custom_counter_event} +/// Event added when some custom logic happens +/// {@endtemplate} +class CustomCounterEvent extends CounterEvent { + /// {@macro custom_counter_event} + const CustomCounterEvent(); + + @override + List get props => []; +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_state.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_state.dart new file mode 100644 index 0000000..79dfd2f --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_bloc/counter_state.dart @@ -0,0 +1,20 @@ +part of 'counter_bloc.dart'; + +/// {@template counter_state} +/// CounterState description +/// {@endtemplate} +abstract class CounterState extends Equatable { + /// {@macro counter_state} + const CounterState(); +} + +/// {@template counter_initial} +/// The initial state of CounterState +/// {@endtemplate} +class CounterInitial extends CounterState { + /// {@macro counter_initial} + const CounterInitial(); + + @override + List get props => []; +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_cubit/counter_cubit.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_cubit/counter_cubit.dart new file mode 100644 index 0000000..3b15c4c --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_cubit/counter_cubit.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/decrement.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/get_current.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/increment.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/reset.dart'; + +part 'counter_state.dart'; + +/// {@template counter_cubit} +/// CounterCubit manages UI depending on counter state. +/// {@endtemplate} +class CounterCubit extends Cubit { + /// {@macro counter_cubit} + CounterCubit({ + required Decrement decrement, + required Increment increment, + required GetCurrent getCurrent, + required Reset reset, + }) : _decrement = decrement, + _increment = increment, + _getCurrent = getCurrent, + _reset = reset, + super(const CounterState(0)); + + final Decrement _decrement; + final Increment _increment; + final GetCurrent _getCurrent; + final Reset _reset; + + /// Decrement counter. + FutureOr decrement([int by = 1]) async { + final result = await _decrement.call(by); + + result.fold( + (integer) => emit(CounterState(integer.value)), + addError, + ); + + return; + } + + /// Increment counter. + FutureOr increment([int by = 1]) async { + final result = await _increment.call(by); + + result.fold( + (integer) => emit(CounterState(integer.value)), + addError, + ); + + return; + } + + /// Get current counter state. + FutureOr getCurrent() async { + final result = await _getCurrent.call(null); + + result.fold( + (integer) => emit(CounterState(integer.value)), + addError, + ); + + return; + } + + /// Reset counter state. + FutureOr reset() async { + final result = await _reset.call(null); + + result.fold( + (integer) => emit(CounterState(integer.value)), + addError, + ); + + return; + } + + @override + void onError(Object error, StackTrace stackTrace) { + emit(state); + super.onError(error, stackTrace); + } +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_cubit/counter_state.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_cubit/counter_state.dart new file mode 100644 index 0000000..50132cd --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/blocs/counter_cubit/counter_state.dart @@ -0,0 +1,14 @@ +part of 'counter_cubit.dart'; + +/// {@template counter_state} +/// CounterState containing counter value +/// {@endtemplate} +class CounterState extends Equatable { + /// {@macro counter_state} + const CounterState(this.value); + + final int value; + + @override + List get props => [value]; +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/counter.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/counter.dart new file mode 100644 index 0000000..0c60985 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/counter.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_app_template/presentation/features/counter/screens/counter_provider.dart'; + +class Counter extends StatelessWidget { + const Counter({super.key}); + + static const String pageName = 'counterPage'; + + @override + Widget build(BuildContext context) => const CounterProvider(); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/screens/counter_provider.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/screens/counter_provider.dart new file mode 100644 index 0000000..5cc4b16 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/screens/counter_provider.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_app_template/core/extensions/build_context_extension.dart'; +import 'package:wyatt_app_template/domain/repositories/counter_repository.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/decrement.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/get_current.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/increment.dart'; +import 'package:wyatt_app_template/domain/usecases/counter/reset.dart'; +import 'package:wyatt_app_template/presentation/features/counter/blocs/counter_cubit/counter_cubit.dart'; +import 'package:wyatt_app_template/presentation/features/counter/screens/widgets/counter_consumer_widget.dart'; +import 'package:wyatt_app_template/presentation/shared/layouts/wyatt_app_template_scaffold.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +/// {@template counter_provider} +/// CounterProvider provides bloc to his children. +/// {@endtemplate} +class CounterProvider extends CubitProviderScreen { + /// {@macro counter_provider} + const CounterProvider({super.key}); + + @override + CounterCubit create(BuildContext context) => CounterCubit( + decrement: Decrement( + counterRepository: repo(context), + ), + increment: Increment( + counterRepository: repo(context), + ), + getCurrent: GetCurrent( + counterRepository: repo(context), + ), + reset: Reset( + counterRepository: repo(context), + ), + ); + + @override + CounterCubit init(BuildContext context, CounterCubit bloc) => + bloc..getCurrent(); + + @override + Widget builder(BuildContext context) => WyattAppTemplateScaffold( + title: Text(context.l10n.counterAppBarTitle), + body: const CounterConsumerWidget(), + fabChildren: [ + FloatingActionButton( + heroTag: 'increment_tag', + onPressed: () => bloc(context).increment(), + child: const Icon(Icons.add), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'increment_10_tag', + onPressed: () => bloc(context).increment(10), + child: const Text('+10'), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'decrement_tag', + onPressed: () => bloc(context).decrement(), + child: const Icon(Icons.remove), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'decrement_10_tag', + onPressed: () => bloc(context).decrement(10), + child: const Text('-10'), + ), + const SizedBox(height: 8), + FloatingActionButton( + heroTag: 'reset_tag', + onPressed: () => bloc(context).reset(), + child: const Icon(Icons.refresh), + ), + ], + ); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/screens/widgets/counter_consumer_widget.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/screens/widgets/counter_consumer_widget.dart new file mode 100644 index 0000000..5f8eba7 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/screens/widgets/counter_consumer_widget.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:wyatt_app_template/core/extensions/build_context_extension.dart'; +import 'package:wyatt_app_template/presentation/features/counter/blocs/counter_cubit/counter_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +/// {@template counter_consumer_widget} +/// CounterConsumerWidget is a stateful widget. Aware of state changes. +/// {@endtemplate} +class CounterConsumerWidget + extends CubitConsumerScreen { + /// {@macro counter_consumer_widget} + const CounterConsumerWidget({super.key}); + + @override + Widget onBuild(BuildContext context, CounterState state) => Text( + context.l10n.youHavePushed(state.value), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline3, + ); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/stateless/counter_widget.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/stateless/counter_widget.dart new file mode 100644 index 0000000..32a7619 --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/counter/stateless/counter_widget.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +/// {@template counter_widget} +/// CounterWidget is a stateless widget. (Not aware of cubit or bloc states) +/// {@endtemplate} +class CounterWidget extends StatelessWidget { + /// {@macro counter_widget} + const CounterWidget({super.key}); + + @override + Widget build(BuildContext context) => Container(); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/home/home.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/home/home.dart new file mode 100644 index 0000000..2b5035f --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/features/home/home.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; +import 'package:wyatt_app_template/core/extensions/build_context_extension.dart'; +import 'package:wyatt_app_template/gen/assets.gen.dart'; +import 'package:wyatt_app_template/presentation/features/counter/counter.dart'; +import 'package:wyatt_app_template/presentation/shared/layouts/wyatt_app_template_scaffold.dart'; + +class Home extends StatelessWidget { + const Home({super.key}); + + static const String pageName = 'homePage'; + + @override + Widget build(BuildContext context) => WyattAppTemplateScaffold( + body: Center( + child: Column( + children: [ + Assets.images.wyattLogo.image(width: 150), + const Gap(30), + ElevatedButton( + child: Text(context.l10n.goToCounter), + onPressed: () => context.pushNamed(Counter.pageName), + ), + ], + ), + ), + ); +} diff --git a/apps/wyatt_app_template/wyatt_app_template/lib/presentation/shared/layouts/wyatt_app_template_scaffold.dart b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/shared/layouts/wyatt_app_template_scaffold.dart new file mode 100644 index 0000000..60e2dee --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/shared/layouts/wyatt_app_template_scaffold.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class WyattAppTemplateScaffold extends StatelessWidget { + const WyattAppTemplateScaffold({ + required this.body, + super.key, + this.title, + this.fabChildren, + }); + + 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_app_template/wyatt_app_template/lib/presentation/shared/widgets/.gitkeep b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/shared/widgets/.gitkeep new file mode 100644 index 0000000..f94cb6f --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/lib/presentation/shared/widgets/.gitkeep @@ -0,0 +1 @@ +# just to keep empty folder in brick generation \ No newline at end of file diff --git a/apps/wyatt_app_template/wyatt_app_template/test/widget_test.dart b/apps/wyatt_app_template/wyatt_app_template/test/widget_test.dart index 3d97111..3a06c0e 100644 --- a/apps/wyatt_app_template/wyatt_app_template/test/widget_test.dart +++ b/apps/wyatt_app_template/wyatt_app_template/test/widget_test.dart @@ -1,30 +1 @@ -// 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_app_template/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (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); - }); -} +// TODO(wyatt): add some tests diff --git a/apps/wyatt_app_template/wyatt_app_template/web/favicon.png b/apps/wyatt_app_template/wyatt_app_template/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/apps/wyatt_app_template/wyatt_app_template/web/favicon.png differ diff --git a/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-192.png b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-192.png differ diff --git a/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-512.png b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-512.png differ diff --git a/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-maskable-192.png b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-maskable-192.png differ diff --git a/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-maskable-512.png b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/apps/wyatt_app_template/wyatt_app_template/web/icons/Icon-maskable-512.png differ diff --git a/apps/wyatt_app_template/wyatt_app_template/web/index.html b/apps/wyatt_app_template/wyatt_app_template/web/index.html new file mode 100644 index 0000000..368f7bb --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + Wyatt App + + + + + + + + + + diff --git a/apps/wyatt_app_template/wyatt_app_template/web/manifest.json b/apps/wyatt_app_template/wyatt_app_template/web/manifest.json new file mode 100644 index 0000000..88f778d --- /dev/null +++ b/apps/wyatt_app_template/wyatt_app_template/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "Wyatt App", + "short_name": "Wyatt App", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "wyatt_description", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}