feat(wyatt_app_template): use brickgen
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:starting_template/core/dependency_injection/get_it.dart';
|
||||
import 'package:starting_template/core/flavors/flavor.dart';
|
||||
import 'package:starting_template/core/utils/app_bloc_observer.dart';
|
||||
|
||||
Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
|
||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
// FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
|
||||
Bloc.observer = AppBlocObserver();
|
||||
|
||||
debugPrint('Flavor: ${Flavor.get()}');
|
||||
|
||||
await GetItInitializer.init();
|
||||
|
||||
runApp(await builder());
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/// Firebase Emulator constants.
|
||||
///
|
||||
/// If you don't use Firebase, it can be safely deleted.
|
||||
abstract class Emulator {
|
||||
static const String firebaseCloudFunctionEnvKey =
|
||||
'EMULATOR_FIREBASE_CLOUD_FUNCTION_PORT';
|
||||
static const String firebaseFirestoreEnvKey =
|
||||
'EMULATOR_FIREBASE_FIRESTORE_PORT';
|
||||
static const String firebaseAuthEnvKey = 'EMULATOR_FIREBASE_AUTH_PORT';
|
||||
static const String firebaseStorageEnvKey = 'EMULATOR_FIREBASE_STORAGE_PORT';
|
||||
static const String hostEnvKey = 'EMULATOR_HOST';
|
||||
|
||||
static const int defaultFirebaseCloudFunctionPort = 5001;
|
||||
static const int defaultFirebaseFirestorePort = 8080;
|
||||
static const int defaultFirebaseAuthPort = 9099;
|
||||
static const int defaultFirebaseStoragePort = 9199;
|
||||
static const defaultHost = 'localhost';
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:starting_template/core/enums/dev_mode.dart';
|
||||
import 'package:starting_template/core/flavors/flavor.dart';
|
||||
import 'package:starting_template/data/data_sources/local/counter_data_source_impl.dart';
|
||||
import 'package:starting_template/domain/data_sources/local/counter_data_source.dart';
|
||||
|
||||
final getIt = GetIt.I;
|
||||
|
||||
/// Service and Data Source locator
|
||||
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.
|
||||
}
|
||||
|
||||
static FutureOr<void> _initReal() async {
|
||||
// Initialize real sources/services
|
||||
}
|
||||
|
||||
static FutureOr<void> init() async {
|
||||
await _initCommon();
|
||||
final flavor = Flavor.get();
|
||||
|
||||
if (flavor.devMode == DevMode.mock) {
|
||||
await _initMocks();
|
||||
} else {
|
||||
await _initReal();
|
||||
}
|
||||
|
||||
await getIt.allReady();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
enum DevMode {
|
||||
mock,
|
||||
emulator,
|
||||
real;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
|
||||
/// Tries to parse String and returns mode. Fallback is returned if there
|
||||
/// is an error during parsing.
|
||||
static DevMode fromString(String? mode, {DevMode fallback = DevMode.mock}) {
|
||||
for (final m in values) {
|
||||
if (m.name == mode) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:starting_template/gen/app_localizations.dart';
|
||||
|
||||
extension BuildContextExtension on BuildContext {
|
||||
AppLocalizations get l10n => AppLocalizations.of(this);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:starting_template/core/enums/dev_mode.dart';
|
||||
|
||||
abstract class Flavor {
|
||||
Flavor._({
|
||||
this.banner,
|
||||
this.bannerColor = Colors.red,
|
||||
this.devMode,
|
||||
}) {
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
static Flavor? _instance;
|
||||
|
||||
final String? banner;
|
||||
final Color bannerColor;
|
||||
final DevMode? devMode;
|
||||
|
||||
/// Returns [Flavor] instance.
|
||||
static Flavor get() {
|
||||
if (_instance == null) {
|
||||
throw Exception('Flavor not initialized!');
|
||||
}
|
||||
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => runtimeType.toString().replaceAll('Flavor', '');
|
||||
}
|
||||
|
||||
class DevelopmentFlavor extends Flavor {
|
||||
factory DevelopmentFlavor() {
|
||||
const modeString = String.fromEnvironment('dev_mode', defaultValue: 'mock');
|
||||
final mode = DevMode.fromString(modeString);
|
||||
|
||||
return DevelopmentFlavor._(devMode: mode);
|
||||
}
|
||||
DevelopmentFlavor._({
|
||||
required DevMode devMode,
|
||||
}) : super._(
|
||||
banner: 'Dev',
|
||||
devMode: devMode,
|
||||
);
|
||||
}
|
||||
|
||||
class StagingFlavor extends Flavor {
|
||||
StagingFlavor()
|
||||
: super._(
|
||||
banner: 'Staging',
|
||||
bannerColor: Colors.green,
|
||||
);
|
||||
}
|
||||
|
||||
class ProductionFlavor extends Flavor {
|
||||
ProductionFlavor() : super._();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:starting_template/presentation/features/counter/counter.dart';
|
||||
import 'package:starting_template/presentation/features/home/home.dart';
|
||||
|
||||
abstract class AppRouter {
|
||||
/// Default transition for all pages
|
||||
static Page<void> defaultTransition(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
Widget child,
|
||||
) =>
|
||||
CupertinoPage<void>(
|
||||
key: state.pageKey,
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Disable transition animation
|
||||
static Page<void> noTransition(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
Widget child,
|
||||
) =>
|
||||
CustomTransitionPage<void>(
|
||||
key: state.pageKey,
|
||||
transitionsBuilder: (_, __, ___, child) => child,
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Defines public routes (no authentication needed).
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// static final publicRoutes = [
|
||||
/// '/',
|
||||
/// '/sign_in',
|
||||
/// '/sign_up',
|
||||
/// ];
|
||||
/// ```
|
||||
static final List<String> publicRoutes = [];
|
||||
|
||||
/// Defines 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,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
const _messageLength = 100;
|
||||
|
||||
class AppBlocObserver extends BlocObserver {
|
||||
AppBlocObserver({
|
||||
this.printEvent = true,
|
||||
this.printError = true,
|
||||
this.printTransition = false,
|
||||
this.printChange = true,
|
||||
this.fullPrint = false,
|
||||
});
|
||||
|
||||
final bool printEvent;
|
||||
final bool printError;
|
||||
final bool printChange;
|
||||
final bool printTransition;
|
||||
final bool fullPrint;
|
||||
|
||||
String sanitize(Object? object) {
|
||||
final message = object.toString();
|
||||
|
||||
return fullPrint
|
||||
? message
|
||||
: message.substring(
|
||||
0,
|
||||
message.length < _messageLength ? message.length : _messageLength,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {
|
||||
super.onEvent(bloc, event);
|
||||
if (printEvent) {
|
||||
debugPrint('onEvent: ${bloc.runtimeType}\n'
|
||||
'> event: ${sanitize(event)}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {
|
||||
if (printError) {
|
||||
debugPrint('onError: ${bloc.runtimeType}\n'
|
||||
'> error: ${sanitize(error)}\n'
|
||||
'$stackTrace');
|
||||
}
|
||||
super.onError(bloc, error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {
|
||||
super.onChange(bloc, change);
|
||||
if (printChange) {
|
||||
debugPrint('onChange: ${bloc.runtimeType}\n'
|
||||
'> currentState: ${sanitize(change.currentState)}\n'
|
||||
'> nextState: ${sanitize(change.nextState)}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onTransition(
|
||||
Bloc<dynamic, dynamic> bloc,
|
||||
Transition<dynamic, dynamic> transition,
|
||||
) {
|
||||
super.onTransition(bloc, transition);
|
||||
if (printTransition) {
|
||||
debugPrint('onTransition: ${bloc.runtimeType}\n'
|
||||
'> currentState: ${sanitize(transition.currentState)}\n'
|
||||
'> event: ${sanitize(transition.event)}\n'
|
||||
'> nextState: ${sanitize(transition.nextState)}');
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:starting_template/data/models/integer_model.dart';
|
||||
import 'package:starting_template/domain/data_sources/local/counter_data_source.dart';
|
||||
import 'package:starting_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:starting_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,
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import 'package:starting_template/domain/data_sources/local/counter_data_source.dart';
|
||||
import 'package:starting_template/domain/entities/integer.dart';
|
||||
import 'package:starting_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,
|
||||
);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import 'package:starting_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)';
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import 'package:starting_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:starting_template/domain/entities/integer.dart';
|
||||
import 'package:starting_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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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:starting_template/domain/entities/integer.dart';
|
||||
import 'package:starting_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:starting_template/domain/entities/integer.dart';
|
||||
import 'package:starting_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:starting_template/domain/entities/integer.dart';
|
||||
import 'package:starting_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,142 @@
|
||||
/// Display Name, localized files. Automatically generated with `task gen:intl`.
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_fr.dart' deferred as app_localizations_fr;
|
||||
|
||||
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||
/// returned by `AppLocalizations.of(context)`.
|
||||
///
|
||||
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||
/// `localizationDelegates` list, and the locales they support in the app's
|
||||
/// `supportedLocales` list. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'gen/app_localizations.dart';
|
||||
///
|
||||
/// return MaterialApp(
|
||||
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||
/// home: MyApplicationHome(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ## Update pubspec.yaml
|
||||
///
|
||||
/// Please make sure to update your pubspec.yaml to include the following
|
||||
/// packages:
|
||||
///
|
||||
/// ```yaml
|
||||
/// dependencies:
|
||||
/// # Internationalization support.
|
||||
/// flutter_localizations:
|
||||
/// sdk: flutter
|
||||
/// intl: any # Use the pinned version from flutter_localizations
|
||||
///
|
||||
/// # Rest of dependencies
|
||||
/// ```
|
||||
///
|
||||
/// ## iOS Applications
|
||||
///
|
||||
/// iOS applications define key application metadata, including supported
|
||||
/// locales, in an Info.plist file that is built into the application bundle.
|
||||
/// To configure the locales supported by your app, you’ll need to edit this
|
||||
/// file.
|
||||
///
|
||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||
/// project’s Runner folder.
|
||||
///
|
||||
/// Next, select the Information Property List item, select Add Item from the
|
||||
/// Editor menu, then select Localizations from the pop-up menu.
|
||||
///
|
||||
/// Select and expand the newly-created Localizations item then, for each
|
||||
/// locale your application supports, add a new item and select the locale
|
||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||
/// property.
|
||||
abstract class AppLocalizations {
|
||||
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||
|
||||
final String localeName;
|
||||
|
||||
static AppLocalizations of(BuildContext context) {
|
||||
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
|
||||
|
||||
/// A list of this localizations delegate along with the default localizations
|
||||
/// delegates.
|
||||
///
|
||||
/// Returns a list of localizations delegates containing this delegate along with
|
||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||
/// and GlobalWidgetsLocalizations.delegate.
|
||||
///
|
||||
/// Additional delegates can be added by appending to this list in
|
||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('fr')
|
||||
];
|
||||
|
||||
/// Texte affiché dans l'AppBar de la page Compteur
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Compteur'**
|
||||
String get counterAppBarTitle;
|
||||
|
||||
/// Message affiché sur la page compteur
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Vous avez appuyé {count} fois sur le bouton !'**
|
||||
String youHavePushed(int count);
|
||||
|
||||
/// Texte affiché dans le bouton ammenant vers la page Compteur
|
||||
///
|
||||
/// In fr, this message translates to:
|
||||
/// **'Aller au Compteur'**
|
||||
String get goToCounter;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
const _AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) {
|
||||
return lookupAppLocalizations(locale);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => <String>['fr'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
Future<AppLocalizations> lookupAppLocalizations(Locale locale) {
|
||||
|
||||
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'fr': return app_localizations_fr.loadLibrary().then((dynamic _) => app_localizations_fr.AppLocalizationsFr());
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.'
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/// Display Name, localized files. Automatically generated with `task gen:intl`.
|
||||
|
||||
import 'app_localizations.dart';
|
||||
|
||||
/// The translations for French (`fr`).
|
||||
class AppLocalizationsFr extends AppLocalizations {
|
||||
AppLocalizationsFr([String locale = 'fr']) : super(locale);
|
||||
|
||||
@override
|
||||
String get counterAppBarTitle => 'Compteur';
|
||||
|
||||
@override
|
||||
String youHavePushed(int count) {
|
||||
return 'Vous avez appuyé $count fois sur le bouton !';
|
||||
}
|
||||
|
||||
@override
|
||||
String get goToCounter => 'Aller au Compteur';
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Invoke "debug painting" (press "p" in the console, choose the
|
||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||
// to see the wireframe for each widget.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:starting_template/bootstrap.dart';
|
||||
import 'package:starting_template/core/flavors/flavor.dart';
|
||||
import 'package:starting_template/presentation/features/app/app.dart';
|
||||
|
||||
void main(List<String> args) {
|
||||
// Define environment
|
||||
DevelopmentFlavor();
|
||||
|
||||
// Initialize environment and variables
|
||||
bootstrap(App.new);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:starting_template/bootstrap.dart';
|
||||
import 'package:starting_template/core/flavors/flavor.dart';
|
||||
import 'package:starting_template/presentation/features/app/app.dart';
|
||||
|
||||
void main(List<String> args) {
|
||||
// Define environment
|
||||
ProductionFlavor();
|
||||
|
||||
// Initialize environment and variables
|
||||
bootstrap(App.new);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:starting_template/bootstrap.dart';
|
||||
import 'package:starting_template/core/flavors/flavor.dart';
|
||||
import 'package:starting_template/presentation/features/app/app.dart';
|
||||
|
||||
void main(List<String> args) {
|
||||
// Define environment
|
||||
StagingFlavor();
|
||||
|
||||
// Initialize environment and variables
|
||||
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:starting_template/core/dependency_injection/get_it.dart';
|
||||
import 'package:starting_template/core/flavors/flavor.dart';
|
||||
import 'package:starting_template/core/routes/router.dart';
|
||||
import 'package:starting_template/data/repositories/counter_repository_impl.dart';
|
||||
import 'package:starting_template/domain/repositories/counter_repository.dart';
|
||||
import 'package:starting_template/gen/app_localizations.dart';
|
||||
import 'package:starting_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: 'Display Name',
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
+27
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
@@ -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 => [];
|
||||
}
|
||||
+20
@@ -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 => [];
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/decrement.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/get_current.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/increment.dart';
|
||||
import 'package:starting_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);
|
||||
}
|
||||
}
|
||||
+14
@@ -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];
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:starting_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();
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:starting_template/core/extensions/build_context_extension.dart';
|
||||
import 'package:starting_template/domain/repositories/counter_repository.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/decrement.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/get_current.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/increment.dart';
|
||||
import 'package:starting_template/domain/usecases/counter/reset.dart';
|
||||
import 'package:starting_template/presentation/features/counter/blocs/counter_cubit/counter_cubit.dart';
|
||||
import 'package:starting_template/presentation/features/counter/screens/widgets/counter_consumer_widget.dart';
|
||||
import 'package:starting_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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:starting_template/core/extensions/build_context_extension.dart';
|
||||
import 'package:starting_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,
|
||||
);
|
||||
}
|
||||
+12
@@ -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:starting_template/core/extensions/build_context_extension.dart';
|
||||
import 'package:starting_template/gen/assets.gen.dart';
|
||||
import 'package:starting_template/presentation/features/counter/counter.dart';
|
||||
import 'package:starting_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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
+27
@@ -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
|
||||
Reference in New Issue
Block a user