feat: add web in new template

This commit is contained in:
Hugo Pointcheval 2023-01-24 15:29:24 +01:00
parent 0c10a900a6
commit 07f16dc512
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
54 changed files with 1248 additions and 59 deletions

View File

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

View File

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

View File

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

View File

@ -1,5 +1,9 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
plugins:
- dart_code_metrics
dart_code_metrics:
anti-patterns:
- long-method

View File

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

View 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

View File

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

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

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

View File

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

View File

@ -14,6 +14,7 @@ enum DevMode {
return m;
}
}
return fallback;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 => [];
}

View File

@ -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 => [];
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View 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"
}
]
}