feat(wyatt_app_template): use brickgen

This commit is contained in:
2023-01-26 23:51:45 +01:00
parent 9638a23345
commit a2b09f9441
141 changed files with 230 additions and 164 deletions
@@ -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;
}
}
@@ -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)}');
}
}
}
@@ -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,
};
@@ -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,
);
}
@@ -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)';
}
@@ -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);
}
}
@@ -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, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects 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,
),
),
);
}
@@ -0,0 +1,27 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
part 'counter_event.dart';
part 'counter_state.dart';
/// {@template counter_bloc}
/// CounterBloc description
/// {@endtemplate}
class CounterBloc extends Bloc<CounterEvent, CounterState> {
/// {@macro counter_bloc}
CounterBloc() : super(const CounterInitial()) {
on<CustomCounterEvent>(_onCustomCounterEvent);
}
FutureOr<void> _onCustomCounterEvent(
CustomCounterEvent event,
Emitter<CounterState> emit,
) async {
// TODO(wyatt): Add custom UI logic
const _ = 1 + 1;
return;
}
}
@@ -0,0 +1,20 @@
part of 'counter_bloc.dart';
/// {@template counter_event}
/// CounterEvent description
/// {@endtemplate}
abstract class CounterEvent extends Equatable {
/// {@macro counter_event}
const CounterEvent();
}
/// {@template custom_counter_event}
/// Event added when some custom logic happens
/// {@endtemplate}
class CustomCounterEvent extends CounterEvent {
/// {@macro custom_counter_event}
const CustomCounterEvent();
@override
List<Object?> get props => [];
}
@@ -0,0 +1,20 @@
part of 'counter_bloc.dart';
/// {@template counter_state}
/// CounterState description
/// {@endtemplate}
abstract class CounterState extends Equatable {
/// {@macro counter_state}
const CounterState();
}
/// {@template counter_initial}
/// The initial state of CounterState
/// {@endtemplate}
class CounterInitial extends CounterState {
/// {@macro counter_initial}
const CounterInitial();
@override
List<Object?> get props => [];
}
@@ -0,0 +1,86 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package: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);
}
}
@@ -0,0 +1,14 @@
part of 'counter_cubit.dart';
/// {@template counter_state}
/// CounterState containing counter value
/// {@endtemplate}
class CounterState extends Equatable {
/// {@macro counter_state}
const CounterState(this.value);
final int value;
@override
List<Object?> get props => [value];
}
@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
import 'package: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();
}
@@ -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),
),
],
);
}
@@ -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,
);
}
@@ -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),
),
],
),
),
);
}
@@ -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