templates constant

Map<String, String> const templates

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