feat: add web in new template
This commit is contained in:
parent
0c10a900a6
commit
07f16dc512
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -4,6 +4,56 @@ wyatt_description
|
||||
|
||||
## Requirements
|
||||
|
||||
- Flutter <https://flutter.dev/>
|
||||
- Taskfile <https://taskfile.dev/>
|
||||
- Trapeze <https://trapeze.dev/> (with `npm install` thanks to `package.json`)
|
||||
* Flutter <https://flutter.dev/>
|
||||
* Taskfile <https://taskfile.dev/>
|
||||
* Trapeze <https://trapeze.dev/> (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=<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.
|
||||
|
@ -1,5 +1,9 @@
|
||||
include: package:wyatt_analysis/analysis_options.flutter.yaml
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- dart_code_metrics
|
||||
|
||||
dart_code_metrics:
|
||||
anti-patterns:
|
||||
- long-method
|
||||
|
@ -0,0 +1 @@
|
||||
# just to keep empty folder in brick generation
|
28
apps/wyatt_app_template/wyatt_app_template/ios/Podfile.lock
Normal file
28
apps/wyatt_app_template/wyatt_app_template/ios/Podfile.lock
Normal file
@ -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
|
@ -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 = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@ -42,6 +46,7 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
4204558B17A62367423769E8 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1F58D6DFA5E148E9B1E5401F /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -72,6 +97,8 @@
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
10872F84FB14C7DD915284C6 /* Pods */,
|
||||
4204558B17A62367423769E8 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -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 */
|
||||
|
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
@ -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<void> bootstrap(FutureOr<Widget> Function() builder) async {
|
||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
// FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
|
||||
Bloc.observer = AppBlocObserver();
|
||||
|
||||
|
@ -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<void> _initCommon() async {
|
||||
// Initialize common sources/services
|
||||
getIt.registerLazySingleton<CounterDataSource>(
|
||||
CounterDataSourceImpl.new,
|
||||
);
|
||||
}
|
||||
|
||||
static FutureOr<void> _initMocks() async {
|
||||
// Initialize mocked sources/services
|
||||
// Initialize mocked sources/services.
|
||||
}
|
||||
|
||||
static FutureOr<void> _initReal() async {
|
||||
|
@ -14,6 +14,7 @@ enum DevMode {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<String> publicRoutes = [];
|
||||
|
||||
/// Defines GoRoute routes.
|
||||
static final List<GoRoute> routes = [];
|
||||
static final List<GoRoute> 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,
|
||||
);
|
||||
}
|
||||
|
@ -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<dynamic, dynamic> 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<dynamic> 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<dynamic> bloc, Change<dynamic> 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)}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Integer> decrement(Integer value) async {
|
||||
final current =
|
||||
IntegerModel.fromJson(json.decode(actual) as Map<String, Object?>);
|
||||
|
||||
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<Integer> increment(Integer value) async {
|
||||
final current =
|
||||
IntegerModel.fromJson(json.decode(actual) as Map<String, Object?>);
|
||||
|
||||
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<Integer> getCurrent() async {
|
||||
final current =
|
||||
IntegerModel.fromJson(json.decode(actual) as Map<String, Object?>);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Integer> reset() async {
|
||||
const newInteger = IntegerModel(value: 0);
|
||||
actual = jsonEncode(newInteger.toJson());
|
||||
|
||||
return newInteger;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
# just to keep empty folder in brick generation
|
@ -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<String, Object?> json) =>
|
||||
_$IntegerModelFromJson(json);
|
||||
}
|
@ -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>(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<String, dynamic> json) {
|
||||
return _IntegerModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$IntegerModel {
|
||||
int get value => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IntegerModelCopyWith<IntegerModel> 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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return _$$_IntegerModelToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IntegerModel implements IntegerModel {
|
||||
const factory _IntegerModel({required final int value}) = _$_IntegerModel;
|
||||
|
||||
factory _IntegerModel.fromJson(Map<String, dynamic> json) =
|
||||
_$_IntegerModel.fromJson;
|
||||
|
||||
@override
|
||||
int get value;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_IntegerModelCopyWith<_$_IntegerModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'integer_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$_IntegerModel _$$_IntegerModelFromJson(Map<String, dynamic> json) =>
|
||||
_$_IntegerModel(
|
||||
value: json['value'] as int,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_IntegerModelToJson(_$_IntegerModel instance) =>
|
||||
<String, dynamic>{
|
||||
'value': instance.value,
|
||||
};
|
@ -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<Integer> decrement({Integer by = const Integer(1)}) =>
|
||||
Result.tryCatchAsync<Integer, AppException, AppException>(
|
||||
() async => _counterDataSource.decrement(by),
|
||||
(error) => error,
|
||||
);
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> increment({Integer by = const Integer(1)}) =>
|
||||
Result.tryCatchAsync<Integer, AppException, AppException>(
|
||||
() async => _counterDataSource.increment(by),
|
||||
(error) => error,
|
||||
);
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> getCurrent() =>
|
||||
Result.tryCatchAsync<Integer, AppException, AppException>(
|
||||
() async => _counterDataSource.getCurrent(),
|
||||
(error) => error,
|
||||
);
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> reset() =>
|
||||
Result.tryCatchAsync<Integer, AppException, AppException>(
|
||||
() async => _counterDataSource.reset(),
|
||||
(error) => error,
|
||||
);
|
||||
}
|
@ -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<Integer> decrement(Integer value);
|
||||
Future<Integer> increment(Integer value);
|
||||
Future<Integer> getCurrent();
|
||||
Future<Integer> reset();
|
||||
}
|
@ -0,0 +1 @@
|
||||
# just to keep empty folder in brick generation
|
@ -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)';
|
||||
}
|
@ -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<Integer> decrement({Integer by = const Integer(1)});
|
||||
FutureOrResult<Integer> increment({Integer by = const Integer(1)});
|
||||
FutureOrResult<Integer> getCurrent();
|
||||
FutureOrResult<Integer> reset();
|
||||
}
|
@ -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<int, Integer> {
|
||||
Decrement({
|
||||
required CounterRepository counterRepository,
|
||||
}) : _counterRepository = counterRepository;
|
||||
|
||||
final CounterRepository _counterRepository;
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> execute(int? params) async {
|
||||
final step = Integer(params ?? 1);
|
||||
|
||||
return _counterRepository.decrement(by: step);
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<void, Integer> {
|
||||
GetCurrent({
|
||||
required CounterRepository counterRepository,
|
||||
}) : _counterRepository = counterRepository;
|
||||
|
||||
final CounterRepository _counterRepository;
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> execute(void params) async =>
|
||||
_counterRepository.getCurrent();
|
||||
}
|
@ -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<int, Integer> {
|
||||
Increment({
|
||||
required CounterRepository counterRepository,
|
||||
}) : _counterRepository = counterRepository;
|
||||
|
||||
final CounterRepository _counterRepository;
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> execute(int? params) async {
|
||||
final step = Integer(params ?? 1);
|
||||
|
||||
return _counterRepository.increment(by: step);
|
||||
}
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<void, Integer> {
|
||||
Reset({
|
||||
required CounterRepository counterRepository,
|
||||
}) : _counterRepository = counterRepository;
|
||||
|
||||
final CounterRepository _counterRepository;
|
||||
|
||||
@override
|
||||
FutureOrResult<Integer> execute(void params) async =>
|
||||
_counterRepository.reset();
|
||||
}
|
@ -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<AssetGenImage> 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<double>? 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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<String> args) {
|
||||
// Define environment
|
||||
DevelopmentFlavor();
|
||||
|
||||
// Initialize environment and variables
|
||||
bootstrap(() async => Container());
|
||||
bootstrap(App.new);
|
||||
}
|
||||
|
@ -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<String> args) {
|
||||
// Define environment
|
||||
ProductionFlavor();
|
||||
|
||||
// Initialize environment and variables
|
||||
bootstrap(() async => Container());
|
||||
bootstrap(App.new);
|
||||
}
|
||||
|
@ -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<String> args) {
|
||||
// Define environment
|
||||
StagingFlavor();
|
||||
|
||||
// Initialize environment and variables
|
||||
bootstrap(() async => Container());
|
||||
bootstrap(App.new);
|
||||
}
|
||||
|
@ -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<CounterRepository>(
|
||||
create: (_) => CounterRepositoryImpl(counterDataSource: getIt()),
|
||||
),
|
||||
],
|
||||
blocProviders: [
|
||||
BlocProvider<CounterBloc>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@ -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<CounterEvent, CounterState> {
|
||||
/// {@macro counter_bloc}
|
||||
CounterBloc() : super(const CounterInitial()) {
|
||||
on<CustomCounterEvent>(_onCustomCounterEvent);
|
||||
}
|
||||
|
||||
FutureOr<void> _onCustomCounterEvent(
|
||||
CustomCounterEvent event,
|
||||
Emitter<CounterState> emit,
|
||||
) async {
|
||||
// TODO(wyatt): Add custom UI logic
|
||||
const _ = 1 + 1;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@ -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<Object?> get props => [];
|
||||
}
|
@ -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<Object?> get props => [];
|
||||
}
|
@ -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<CounterState> {
|
||||
/// {@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<void> decrement([int by = 1]) async {
|
||||
final result = await _decrement.call(by);
|
||||
|
||||
result.fold(
|
||||
(integer) => emit(CounterState(integer.value)),
|
||||
addError,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// Increment counter.
|
||||
FutureOr<void> 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<void> getCurrent() async {
|
||||
final result = await _getCurrent.call(null);
|
||||
|
||||
result.fold(
|
||||
(integer) => emit(CounterState(integer.value)),
|
||||
addError,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// Reset counter state.
|
||||
FutureOr<void> 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);
|
||||
}
|
||||
}
|
@ -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<Object?> get props => [value];
|
||||
}
|
@ -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();
|
||||
}
|
@ -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<CounterCubit, CounterState> {
|
||||
/// {@macro counter_provider}
|
||||
const CounterProvider({super.key});
|
||||
|
||||
@override
|
||||
CounterCubit create(BuildContext context) => CounterCubit(
|
||||
decrement: Decrement(
|
||||
counterRepository: repo<CounterRepository>(context),
|
||||
),
|
||||
increment: Increment(
|
||||
counterRepository: repo<CounterRepository>(context),
|
||||
),
|
||||
getCurrent: GetCurrent(
|
||||
counterRepository: repo<CounterRepository>(context),
|
||||
),
|
||||
reset: Reset(
|
||||
counterRepository: repo<CounterRepository>(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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
@ -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<CounterCubit, CounterState> {
|
||||
/// {@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,
|
||||
);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@ -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<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,
|
||||
);
|
||||
}
|
@ -0,0 +1 @@
|
||||
# just to keep empty folder in brick generation
|
@ -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
|
||||
|
BIN
apps/wyatt_app_template/wyatt_app_template/web/favicon.png
Normal file
BIN
apps/wyatt_app_template/wyatt_app_template/web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
58
apps/wyatt_app_template/wyatt_app_template/web/index.html
Normal file
58
apps/wyatt_app_template/wyatt_app_template/web/index.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="wyatt_description">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="Wyatt App">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>Wyatt App</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<script>
|
||||
// The value below is injected by flutter build, do not touch.
|
||||
var serviceWorkerVersion = null;
|
||||
</script>
|
||||
<!-- This script adds the flutter initialization JS code -->
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener('load', function(ev) {
|
||||
// Download main.dart.js
|
||||
_flutter.loader.loadEntrypoint({
|
||||
serviceWorker: {
|
||||
serviceWorkerVersion: serviceWorkerVersion,
|
||||
}
|
||||
}).then(function(engineInitializer) {
|
||||
return engineInitializer.initializeEngine();
|
||||
}).then(function(appRunner) {
|
||||
return appRunner.runApp();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
35
apps/wyatt_app_template/wyatt_app_template/web/manifest.json
Normal file
35
apps/wyatt_app_template/wyatt_app_template/web/manifest.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user