templates constant
Map of template asset paths to raw stub file contents.
Implementation
static const Map<String, String> templates = {
'assets/icon_assets.dart.stub': r'''
class IconAssets {
// Private constructor prevents instantiation (e.g., IconAssets())
IconAssets._();
// --- Base Paths ---
static const String _icons = 'assets/icons';
// Example: static const String menu = '$_icons/menu.png';
}
''',
'assets/image_assets.dart.stub': r'''
class ImageAssets {
// Private constructor prevents instantiation (e.g., ImageAssets())
ImageAssets._();
// --- Base Paths ---
// Keeping base paths private helps prevent typos across multiple assets
static const String _images = 'assets/images';
static const String logo = '$_images/logo.png';
}
''',
'assets/svg_assets.dart.stub': r'''
class SvgAssets {
// Private constructor prevents instantiation (e.g., SvgAssets())
SvgAssets._();
// --- Base Paths ---
static const String _svgs = 'assets/svgs';
// Example: static const String logo = '$_svgs/logo.svg';
}
''',
'bloc/bloc.dart.stub': r'''
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part '{{name_snake}}_event.dart';
part '{{name_snake}}_state.dart';
class {{name_pascal}}Bloc extends Bloc<{{name_pascal}}Event, {{name_pascal}}State> {
{{name_pascal}}Bloc() : super({{name_pascal}}Initial()) {
on<{{name_pascal}}Event>((event, emit) {
// TODO: implement event handler
});
}
}
''',
'bloc/event.dart.stub': r'''
part of '{{name_snake}}_bloc.dart';
sealed class {{name_pascal}}Event extends Equatable {
const {{name_pascal}}Event();
@override
List<Object> get props => [];
}
''',
'bloc/state.dart.stub': r'''
part of '{{name_snake}}_bloc.dart';
sealed class {{name_pascal}}State extends Equatable {
const {{name_pascal}}State();
@override
List<Object> get props => [];
}
final class {{name_pascal}}Initial extends {{name_pascal}}State {}
''',
'error/either.dart.stub': r'''
abstract class Either<L, R> {
T fold<T>(T Function(L left) leftFn, T Function(R right) rightFn);
}
class Left<L, R> extends Either<L, R> {
final L value;
Left(this.value);
@override
T fold<T>(T Function(L left) leftFn, T Function(R right) rightFn) {
return leftFn(value);
}
}
class Right<L, R> extends Either<L, R> {
final R value;
Right(this.value);
@override
T fold<T>(T Function(L left) leftFn, T Function(R right) rightFn) {
return rightFn(value);
}
}
''',
'error/failure.dart.stub': r'''
abstract class Failure {
final String message;
const Failure(this.message);
}
class NetworkFailure extends Failure {
const NetworkFailure(super.message);
}
class UnauthorizedFailure extends Failure {
const UnauthorizedFailure() : super('Unauthorized');
}
class ServerFailure extends Failure {
const ServerFailure(super.message);
}
class UnknownFailure extends Failure {
const UnknownFailure() : super('Something went wrong');
}
class BadRequestFailure extends Failure {
const BadRequestFailure(super.message);
}
class CacheFailure extends Failure {
const CacheFailure(super.message);
}
''',
'extensions/context_extensions.dart.stub': r'''
import 'package:flutter/material.dart';
/// ============================
/// MediaQuery Extensions
/// ============================
extension MediaQueryX on BuildContext {
/// MediaQueryData
MediaQueryData get mediaQuery => MediaQuery.of(this);
/// Size
Size get size => mediaQuery.size;
double get width => size.width;
double get height => size.height;
/// Orientation
Orientation get orientation => mediaQuery.orientation;
bool get isPortrait => orientation == Orientation.portrait;
bool get isLandscape => orientation == Orientation.landscape;
/// Device type helpers
bool get isMobile => width < 600;
bool get isTablet => width >= 600 && width < 1024;
bool get isDesktop => width >= 1024;
/// Padding & Insets
EdgeInsets get padding => mediaQuery.padding;
EdgeInsets get viewInsets => mediaQuery.viewInsets;
EdgeInsets get viewPadding => mediaQuery.viewPadding;
/// Safe area sizes
double get safeTop => padding.top;
double get safeBottom => padding.bottom;
/// Text scale factor
double get textScaleFactor => mediaQuery.textScaleFactor;
/// Platform brightness
Brightness get platformBrightness => mediaQuery.platformBrightness;
bool get isSystemDarkMode => platformBrightness == Brightness.dark;
}
/// ============================
/// Theme Extensions
/// ============================
extension ThemeX on BuildContext {
/// ThemeData
ThemeData get theme => Theme.of(this);
/// Colors
ColorScheme get colorScheme => theme.colorScheme;
Color get primaryColor => theme.primaryColor;
Color get scaffoldBackgroundColor => theme.scaffoldBackgroundColor;
Color get dividerColor => theme.dividerColor;
/// Text themes
TextTheme get textTheme => theme.textTheme;
TextTheme get primaryTextTheme => theme.primaryTextTheme;
/// Icon theme
IconThemeData get iconTheme => theme.iconTheme;
/// AppBar theme
AppBarThemeData get appBarTheme => theme.appBarTheme;
/// Brightness
Brightness get brightness => theme.brightness;
bool get isDarkMode => brightness == Brightness.dark;
bool get isLightMode => brightness == Brightness.light;
}
''',
'model/entity.dart.stub': r'''
import 'package:equatable/equatable.dart';
class {{name_pascal}}Entity extends Equatable {
const {{name_pascal}}Entity();
@override
List<Object?> get props => [];
}
''',
'model/model.dart.stub': r'''
class {{name_pascal}}Model {
{{name_pascal}}Model();
factory {{name_pascal}}Model.fromJson(Map<String, dynamic> json) {
return {{name_pascal}}Model();
}
Map<String, dynamic> toJson() {
return {};
}
}
''',
'network/api_client.dart.stub': r'''
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
/// Configured [Dio] HTTP client for handling API requests.
class ApiClient {
late final Dio dio;
// 🔒 Private constructor
ApiClient._internal({required String baseUrl, String? authToken}) {
final options = BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
responseType: ResponseType.json,
headers: {
'Content-Type': 'application/json',
if (authToken != null) 'Authorization': 'Bearer $authToken',
},
);
dio = Dio(options);
_addInterceptors();
}
// 🌍 Singleton instance
static ApiClient? _instance;
/// Call this ONCE (usually in main or DI setup)
static ApiClient initialize({required String baseUrl, String? authToken}) {
_instance ??= ApiClient._internal(baseUrl: baseUrl, authToken: authToken);
return _instance!;
}
/// Access anywhere after initialize()
static ApiClient get instance {
assert(
_instance != null,
'ApiClient is not initialized. Call ApiClient.initialize() first.',
);
return _instance!;
}
Dio get client => dio;
// 🔄 Update auth token dynamically
void updateAuthToken(String token) {
dio.options.headers['Authorization'] = 'Bearer $token';
}
void clearAuthToken() {
dio.options.headers.remove('Authorization');
}
// ---------- INTERCEPTORS ----------
void _addInterceptors() {
if (kDebugMode) {
dio.interceptors.add(
PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
error: true,
compact: true,
maxWidth: 90,
),
);
}
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// Token refresh logic can go here
return handler.next(options);
},
onResponse: (response, handler) {
return handler.next(response);
},
onError: (DioException e, handler) {
return handler.next(e);
},
),
);
}
// ---------- HTTP METHODS ----------
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParams,
Options? options,
CancelToken? cancelToken,
}) {
return dio.get<T>(
path,
queryParameters: queryParams,
options: options,
cancelToken: cancelToken,
);
}
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParams,
Options? options,
CancelToken? cancelToken,
}) {
return dio.post<T>(
path,
data: data,
queryParameters: queryParams,
options: options,
cancelToken: cancelToken,
);
}
Future<Response<T>> put<T>(
String path, {
dynamic data,
Options? options,
CancelToken? cancelToken,
}) {
return dio.put<T>(
path,
data: data,
options: options,
cancelToken: cancelToken,
);
}
Future<Response<T>> patch<T>(
String path, {
dynamic data,
Options? options,
CancelToken? cancelToken,
}) {
return dio.patch<T>(
path,
data: data,
options: options,
cancelToken: cancelToken,
);
}
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Options? options,
CancelToken? cancelToken,
}) {
return dio.delete<T>(
path,
data: data,
options: options,
cancelToken: cancelToken,
);
}
Future<Response<T>> head<T>(
String path, {
Map<String, dynamic>? queryParams,
Options? options,
CancelToken? cancelToken,
}) {
return dio.head<T>(
path,
queryParameters: queryParams,
options: options,
cancelToken: cancelToken,
);
}
Future<Response<T>> options<T>(
String path, {
Options? options,
CancelToken? cancelToken,
}) {
return dio.request<T>(
path,
options: (options ?? Options()).copyWith(method: 'OPTIONS'),
cancelToken: cancelToken,
);
}
// ---------- FILE UPLOAD ----------
Future<Response<T>> upload<T>(
String path, {
required FormData formData,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
}) {
return dio.post<T>(
path,
data: formData,
options:
options ?? Options(headers: {'Content-Type': 'multipart/form-data'}),
cancelToken: cancelToken,
onSendProgress: onSendProgress,
);
}
// ---------- FILE DOWNLOAD ----------
Future<Response> download(
String url,
String savePath, {
ProgressCallback? onReceiveProgress,
CancelToken? cancelToken,
Options? options,
}) {
return dio.download(
url,
savePath,
onReceiveProgress: onReceiveProgress,
cancelToken: cancelToken,
options: options,
);
}
// ---------- CANCEL TOKEN ----------
CancelToken createCancelToken() => CancelToken();
}
''',
'network/dio_error_mapper.dart.stub': r'''
import 'package:dio/dio.dart';
import '../errors/failure.dart';
Failure mapDioError(DioException e) {
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout) {
return const NetworkFailure('Connection timeout');
}
final status = e.response?.statusCode;
if (status == 400) {
return BadRequestFailure(e.response?.data['message'] ?? 'Bad request');
}
if (status == 401) {
return const UnauthorizedFailure();
}
if (status != null && status >= 500) {
return ServerFailure('Server error ($status)');
}
return UnknownFailure();
}
''',
'project/README.md.stub': r'''
# {{project_name}}
A new Flutter project created with flo_cli.
## Configuration
- **Architecture:** {{architecture}}
- **State Management:** {{state_management}}
## Getting Started
This project is a starting point for a Flutter application.
''',
'project/main.dart.stub': r'''
import 'package:flutter/material.dart';
import 'package:{{project_name}}/core/theme/app_theme.dart';
import 'package:{{project_name}}/core/routes/app_router.dart';
void main() {
runApp(const {{project_name_pascal}}());
}
class {{project_name_pascal}} extends StatelessWidget {
const {{project_name_pascal}}({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: '{{project_name}}',
theme: AppTheme.light,
darkTheme: AppTheme.dark,
routerConfig: AppRouter.instance.router,
);
}
}
''',
'repository/repository.dart.stub': r'''
abstract class {{name_pascal}}Repository {
// TODO: Define methods
}
''',
'repository/repository_impl.dart.stub': r'''
import '../../domain/repositories/{{name_snake}}_repository.dart';
class {{name_pascal}}RepositoryImpl implements {{name_pascal}}Repository {
{{name_pascal}}RepositoryImpl();
// TODO: Implement methods
}
''',
'routes/app_router.dart.stub': r'''
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'app_routes.dart';
class AppRouter {
AppRouter._();
static final AppRouter instance = AppRouter._();
late final GoRouter router = GoRouter(
initialLocation: AppRoutes.splashPath,
debugLogDiagnostics: true,
routes: [
GoRoute(
name: AppRoutes.splash,
path: AppRoutes.splashPath,
//TODO: Add your first screen here
builder: (context, state) => const Placeholder(),
),
],
errorBuilder: (context, state) => Scaffold(
body: Center(
child: Text('Route not found: ${state.uri}'),
),
),
);
}
''',
'routes/app_routes.dart.stub': r'''
class AppRoutes {
AppRoutes._();
// Route Names
static const String splash = 'splash';
// Route Paths
static const String splashPath = '/';
}
''',
'screen/screen.dart.stub': r'''
import 'package:flutter/material.dart';
class {{name_pascal}}Screen extends StatelessWidget {
const {{name_pascal}}Screen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('{{name_pascal}}Screen'),
),
body: const Center(
child: Text('{{name_pascal}}Screen'),
),
);
}
}
''',
'theme/app_palette.dart.stub': r'''
import 'package:flutter/material.dart';
/// Defines the color palette for the application.
///
/// This class contains only static const [Color] values.
/// Do not hardcode colors in the theme or UI components directly;
/// always reference colors from this palette.
class AppPalette {
// Prevent instantiation
AppPalette._();
// --- Light Theme Colors ---
/// Primary brand color for the light theme.
static const Color primaryLight = Color(0xFF6750A4);
/// Secondary brand color for the light theme.
static const Color secondaryLight = Color(0xFF625B71);
/// Background color for the light theme.
static const Color backgroundLight = Color(0xFFFFFBFE);
/// Surface color for the light theme (cards, dialogs, etc.).
static const Color surfaceLight = Color(0xFFFFFBFE);
/// Primary text color for the light theme.
static const Color textPrimaryLight = Color(0xFF1C1B1F);
/// Secondary text color for the light theme.
static const Color textSecondaryLight = Color(0xFF49454F);
// --- Dark Theme Colors ---
/// Primary brand color for the dark theme.
static const Color primaryDark = Color(0xFFD0BCFF);
/// Secondary brand color for the dark theme.
static const Color secondaryDark = Color(0xFFCCC2DC);
/// Background color for the dark theme.
static const Color backgroundDark = Color(0xFF1C1B1F);
/// Surface color for the dark theme (cards, dialogs, etc.).
static const Color surfaceDark = Color(0xFF1C1B1F);
/// Primary text color for the dark theme.
static const Color textPrimaryDark = Color(0xFFE6E1E5);
/// Secondary text color for the dark theme.
static const Color textSecondaryDark = Color(0xFFCAC4D0);
// --- Semantic Colors (Shared) ---
/// Color representing a successful action or state.
static const Color success = Color(0xFF388E3C);
/// Color representing a warning state.
static const Color warning = Color(0xFFF57C00);
/// Color representing an error or failure state.
static const Color error = Color(0xFFB3261E);
/// Standard border color.
static const Color border = Color(0xFF79747E);
/// Standard divider color.
static const Color divider = Color(0xFFCAC4D0);
}
''',
'theme/app_theme.dart.stub': r'''
import 'package:flutter/material.dart';
import 'app_palette.dart';
/// Provides the light and dark [ThemeData] for the application.
///
/// This class utilizes [AppPalette] exclusively for colors and ensures
/// Material 3 is enabled across the application.
class AppTheme {
// Prevent instantiation
AppTheme._();
/// The light theme configuration.
static ThemeData get light {
return ThemeData(
useMaterial3: true,
scaffoldBackgroundColor: AppPalette.backgroundLight,
colorScheme: const ColorScheme.light(
primary: AppPalette.primaryLight,
secondary: AppPalette.secondaryLight,
surface: AppPalette.surfaceLight,
error: AppPalette.error,
onPrimary: AppPalette.surfaceLight,
onSecondary: AppPalette.surfaceLight,
onSurface: AppPalette.textPrimaryLight,
onError: AppPalette.surfaceLight,
),
appBarTheme: const AppBarTheme(
backgroundColor: AppPalette.primaryLight,
foregroundColor: AppPalette.surfaceLight,
elevation: 0,
centerTitle: true,
),
textTheme: const TextTheme(
displayLarge: TextStyle(color: AppPalette.textPrimaryLight),
displayMedium: TextStyle(color: AppPalette.textPrimaryLight),
displaySmall: TextStyle(color: AppPalette.textPrimaryLight),
headlineLarge: TextStyle(color: AppPalette.textPrimaryLight),
headlineMedium: TextStyle(color: AppPalette.textPrimaryLight),
headlineSmall: TextStyle(color: AppPalette.textPrimaryLight),
titleLarge: TextStyle(color: AppPalette.textPrimaryLight),
titleMedium: TextStyle(color: AppPalette.textPrimaryLight),
titleSmall: TextStyle(color: AppPalette.textPrimaryLight),
bodyLarge: TextStyle(color: AppPalette.textPrimaryLight),
bodyMedium: TextStyle(color: AppPalette.textPrimaryLight),
bodySmall: TextStyle(color: AppPalette.textSecondaryLight),
labelLarge: TextStyle(color: AppPalette.textPrimaryLight),
labelMedium: TextStyle(color: AppPalette.textPrimaryLight),
labelSmall: TextStyle(color: AppPalette.textSecondaryLight),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppPalette.primaryLight,
foregroundColor: AppPalette.surfaceLight,
textStyle: const TextStyle(fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppPalette.primaryLight,
side: const BorderSide(color: AppPalette.primaryLight),
textStyle: const TextStyle(fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppPalette.surfaceLight,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.primaryLight, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.error),
),
labelStyle: const TextStyle(color: AppPalette.textSecondaryLight),
hintStyle: const TextStyle(color: AppPalette.textSecondaryLight),
),
dividerTheme: const DividerThemeData(
color: AppPalette.divider,
thickness: 1,
space: 1,
),
);
}
/// The dark theme configuration.
static ThemeData get dark {
return ThemeData(
useMaterial3: true,
scaffoldBackgroundColor: AppPalette.backgroundDark,
colorScheme: const ColorScheme.dark(
primary: AppPalette.primaryDark,
secondary: AppPalette.secondaryDark,
surface: AppPalette.surfaceDark,
error: AppPalette.error,
onPrimary: AppPalette.surfaceDark,
onSecondary: AppPalette.surfaceDark,
onSurface: AppPalette.textPrimaryDark,
onError: AppPalette.surfaceDark,
),
appBarTheme: const AppBarTheme(
backgroundColor: AppPalette.surfaceDark,
foregroundColor: AppPalette.textPrimaryDark,
elevation: 0,
centerTitle: true,
),
textTheme: const TextTheme(
displayLarge: TextStyle(color: AppPalette.textPrimaryDark),
displayMedium: TextStyle(color: AppPalette.textPrimaryDark),
displaySmall: TextStyle(color: AppPalette.textPrimaryDark),
headlineLarge: TextStyle(color: AppPalette.textPrimaryDark),
headlineMedium: TextStyle(color: AppPalette.textPrimaryDark),
headlineSmall: TextStyle(color: AppPalette.textPrimaryDark),
titleLarge: TextStyle(color: AppPalette.textPrimaryDark),
titleMedium: TextStyle(color: AppPalette.textPrimaryDark),
titleSmall: TextStyle(color: AppPalette.textPrimaryDark),
bodyLarge: TextStyle(color: AppPalette.textPrimaryDark),
bodyMedium: TextStyle(color: AppPalette.textPrimaryDark),
bodySmall: TextStyle(color: AppPalette.textSecondaryDark),
labelLarge: TextStyle(color: AppPalette.textPrimaryDark),
labelMedium: TextStyle(color: AppPalette.textPrimaryDark),
labelSmall: TextStyle(color: AppPalette.textSecondaryDark),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppPalette.primaryDark,
foregroundColor: AppPalette.surfaceDark,
textStyle: const TextStyle(fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppPalette.primaryDark,
side: const BorderSide(color: AppPalette.primaryDark),
textStyle: const TextStyle(fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppPalette.surfaceDark,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.primaryDark, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppPalette.error),
),
labelStyle: const TextStyle(color: AppPalette.textSecondaryDark),
hintStyle: const TextStyle(color: AppPalette.textSecondaryDark),
),
dividerTheme: const DividerThemeData(
color: AppPalette.divider,
thickness: 1,
space: 1,
),
);
}
}
''',
};