This commit is contained in:
2025-09-16 23:14:35 +07:00
parent be2ad0a8fd
commit 9ebe7c2919
55 changed files with 5953 additions and 893 deletions

View File

@@ -0,0 +1,80 @@
/// Application-wide constants
class AppConstants {
// Private constructor to prevent instantiation
AppConstants._();
// API Configuration
static const String apiBaseUrl = 'https://api.example.com'; // Replace with actual API base URL
static const String apiVersion = 'v1';
static const String scansEndpoint = '/api/scans';
// Network Timeouts (in milliseconds)
static const int connectionTimeout = 30000; // 30 seconds
static const int receiveTimeout = 30000; // 30 seconds
static const int sendTimeout = 30000; // 30 seconds
// Local Storage Keys
static const String scanHistoryBox = 'scan_history';
static const String settingsBox = 'settings';
static const String userPreferencesKey = 'user_preferences';
// Scanner Configuration
static const List<String> supportedBarcodeFormats = [
'CODE_128',
'CODE_39',
'CODE_93',
'EAN_13',
'EAN_8',
'UPC_A',
'UPC_E',
'QR_CODE',
'DATA_MATRIX',
];
// UI Configuration
static const int maxHistoryItems = 100;
static const int scanResultDisplayDuration = 3; // seconds
// Form Field Labels
static const String field1Label = 'Field 1';
static const String field2Label = 'Field 2';
static const String field3Label = 'Field 3';
static const String field4Label = 'Field 4';
// Error Messages
static const String networkErrorMessage = 'Network error occurred. Please check your connection.';
static const String serverErrorMessage = 'Server error occurred. Please try again later.';
static const String unknownErrorMessage = 'An unexpected error occurred.';
static const String noDataMessage = 'No data available';
static const String scannerPermissionMessage = 'Camera permission is required to scan barcodes.';
// Success Messages
static const String saveSuccessMessage = 'Data saved successfully!';
static const String printSuccessMessage = 'Print job completed successfully!';
// App Info
static const String appName = 'Barcode Scanner';
static const String appVersion = '1.0.0';
static const String appDescription = 'Simple barcode scanner with form data entry';
// Animation Durations
static const Duration shortAnimationDuration = Duration(milliseconds: 200);
static const Duration mediumAnimationDuration = Duration(milliseconds: 400);
static const Duration longAnimationDuration = Duration(milliseconds: 600);
// Spacing and Sizes
static const double defaultPadding = 16.0;
static const double smallPadding = 8.0;
static const double largePadding = 24.0;
static const double borderRadius = 8.0;
static const double buttonHeight = 48.0;
static const double textFieldHeight = 56.0;
// Scanner View Configuration
static const double scannerAspectRatio = 1.0;
static const double scannerBorderWidth = 2.0;
// Print Configuration
static const String printJobName = 'Barcode Scan Data';
static const double printPageMargin = 72.0; // 1 inch in points
}

7
lib/core/core.dart Normal file
View File

@@ -0,0 +1,7 @@
// Core module exports
export 'constants/app_constants.dart';
export 'errors/exceptions.dart';
export 'errors/failures.dart';
export 'network/api_client.dart';
export 'theme/app_theme.dart';
export 'routing/app_router.dart';

View File

@@ -0,0 +1,123 @@
/// Base class for all exceptions in the application
/// Exceptions are thrown during runtime and should be caught and converted to failures
abstract class AppException implements Exception {
final String message;
final String? code;
const AppException(this.message, {this.code});
@override
String toString() => 'AppException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown when there's a server-related error
/// This includes HTTP errors, API response errors, etc.
class ServerException extends AppException {
const ServerException(super.message, {super.code});
@override
String toString() => 'ServerException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown when there's a network-related error
/// This includes connection timeouts, no internet connection, etc.
class NetworkException extends AppException {
const NetworkException(super.message, {super.code});
@override
String toString() => 'NetworkException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown when there's a local storage error
/// This includes Hive errors, file system errors, etc.
class CacheException extends AppException {
const CacheException(super.message, {super.code});
@override
String toString() => 'CacheException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown when input validation fails
class ValidationException extends AppException {
final Map<String, String>? fieldErrors;
const ValidationException(
super.message, {
super.code,
this.fieldErrors,
});
@override
String toString() {
var result = 'ValidationException: $message${code != null ? ' (Code: $code)' : ''}';
if (fieldErrors != null && fieldErrors!.isNotEmpty) {
result += '\nField errors: ${fieldErrors.toString()}';
}
return result;
}
}
/// Exception thrown when a required permission is denied
class PermissionException extends AppException {
final String permissionType;
const PermissionException(
super.message,
this.permissionType, {
super.code,
});
@override
String toString() =>
'PermissionException: $message (Permission: $permissionType)${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown when scanning operation fails
class ScannerException extends AppException {
const ScannerException(super.message, {super.code});
@override
String toString() => 'ScannerException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown when printing operation fails
class PrintException extends AppException {
const PrintException(super.message, {super.code});
@override
String toString() => 'PrintException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown for JSON parsing errors
class JsonException extends AppException {
const JsonException(super.message, {super.code});
@override
String toString() => 'JsonException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception thrown for format-related errors (e.g., invalid barcode format)
class FormatException extends AppException {
final String expectedFormat;
final String receivedFormat;
const FormatException(
super.message,
this.expectedFormat,
this.receivedFormat, {
super.code,
});
@override
String toString() =>
'FormatException: $message (Expected: $expectedFormat, Received: $receivedFormat)${code != null ? ' (Code: $code)' : ''}';
}
/// Generic exception for unexpected errors
class UnknownException extends AppException {
const UnknownException([super.message = 'An unexpected error occurred', String? code])
: super(code: code);
@override
String toString() => 'UnknownException: $message${code != null ? ' (Code: $code)' : ''}';
}

View File

@@ -0,0 +1,79 @@
import 'package:equatable/equatable.dart';
/// Base class for all failures in the application
/// Failures represent errors that can be handled gracefully
abstract class Failure extends Equatable {
final String message;
const Failure(this.message);
@override
List<Object> get props => [message];
}
/// Failure that occurs when there's a server-related error
/// This includes HTTP errors, API errors, etc.
class ServerFailure extends Failure {
const ServerFailure(super.message);
@override
String toString() => 'ServerFailure: $message';
}
/// Failure that occurs when there's a network-related error
/// This includes connection timeouts, no internet, etc.
class NetworkFailure extends Failure {
const NetworkFailure(super.message);
@override
String toString() => 'NetworkFailure: $message';
}
/// Failure that occurs when there's a local storage error
/// This includes cache errors, database errors, etc.
class CacheFailure extends Failure {
const CacheFailure(super.message);
@override
String toString() => 'CacheFailure: $message';
}
/// Failure that occurs when input validation fails
class ValidationFailure extends Failure {
const ValidationFailure(super.message);
@override
String toString() => 'ValidationFailure: $message';
}
/// Failure that occurs when a required permission is denied
class PermissionFailure extends Failure {
const PermissionFailure(super.message);
@override
String toString() => 'PermissionFailure: $message';
}
/// Failure that occurs when scanning operation fails
class ScannerFailure extends Failure {
const ScannerFailure(super.message);
@override
String toString() => 'ScannerFailure: $message';
}
/// Failure that occurs when printing operation fails
class PrintFailure extends Failure {
const PrintFailure(super.message);
@override
String toString() => 'PrintFailure: $message';
}
/// Generic failure for unexpected errors
class UnknownFailure extends Failure {
const UnknownFailure([super.message = 'An unexpected error occurred']);
@override
String toString() => 'UnknownFailure: $message';
}

View File

@@ -0,0 +1,175 @@
import 'package:dio/dio.dart';
import '../constants/app_constants.dart';
import '../errors/exceptions.dart';
/// API client for making HTTP requests using Dio
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(
BaseOptions(
baseUrl: AppConstants.apiBaseUrl,
connectTimeout: const Duration(milliseconds: AppConstants.connectionTimeout),
receiveTimeout: const Duration(milliseconds: AppConstants.receiveTimeout),
sendTimeout: const Duration(milliseconds: AppConstants.sendTimeout),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
// Add request/response interceptors for logging and error handling
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// Log request details in debug mode
handler.next(options);
},
onResponse: (response, handler) {
// Log response details in debug mode
handler.next(response);
},
onError: (error, handler) {
// Handle different types of errors
_handleDioError(error);
handler.next(error);
},
),
);
}
/// Make a GET request
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.get<T>(
path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _handleDioError(e);
}
}
/// Make a POST request
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _handleDioError(e);
}
}
/// Make a PUT request
Future<Response<T>> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.put<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _handleDioError(e);
}
}
/// Make a DELETE request
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _handleDioError(e);
}
}
/// Handle Dio errors and convert them to custom exceptions
Exception _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return const NetworkException('Connection timeout. Please check your internet connection.');
case DioExceptionType.badResponse:
final statusCode = error.response?.statusCode;
final message = error.response?.data?['message'] ?? 'Server error occurred';
if (statusCode != null) {
if (statusCode >= 400 && statusCode < 500) {
return ServerException('Client error: $message (Status: $statusCode)');
} else if (statusCode >= 500) {
return ServerException('Server error: $message (Status: $statusCode)');
}
}
return ServerException('HTTP error: $message');
case DioExceptionType.cancel:
return const NetworkException('Request was cancelled');
case DioExceptionType.connectionError:
return const NetworkException('No internet connection. Please check your network settings.');
case DioExceptionType.badCertificate:
return const NetworkException('Certificate verification failed');
case DioExceptionType.unknown:
default:
return ServerException('An unexpected error occurred: ${error.message}');
}
}
/// Add authorization header
void addAuthorizationHeader(String token) {
_dio.options.headers['Authorization'] = 'Bearer $token';
}
/// Remove authorization header
void removeAuthorizationHeader() {
_dio.options.headers.remove('Authorization');
}
/// Update base URL (useful for different environments)
void updateBaseUrl(String newBaseUrl) {
_dio.options.baseUrl = newBaseUrl;
}
}

View File

@@ -0,0 +1,211 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../features/scanner/presentation/pages/home_page.dart';
import '../../features/scanner/presentation/pages/detail_page.dart';
/// Application router configuration using GoRouter
final GoRouter appRouter = GoRouter(
initialLocation: '/',
debugLogDiagnostics: true,
routes: [
// Home route - Main scanner screen
GoRoute(
path: '/',
name: 'home',
builder: (BuildContext context, GoRouterState state) {
return const HomePage();
},
),
// Detail route - Edit scan data
GoRoute(
path: '/detail/:barcode',
name: 'detail',
builder: (BuildContext context, GoRouterState state) {
final barcode = state.pathParameters['barcode']!;
return DetailPage(barcode: barcode);
},
redirect: (BuildContext context, GoRouterState state) {
final barcode = state.pathParameters['barcode'];
// Ensure barcode is not empty
if (barcode == null || barcode.trim().isEmpty) {
return '/';
}
return null; // No redirect needed
},
),
// Settings route (optional for future expansion)
GoRoute(
path: '/settings',
name: 'settings',
builder: (BuildContext context, GoRouterState state) {
return const SettingsPlaceholderPage();
},
),
// About route (optional for future expansion)
GoRoute(
path: '/about',
name: 'about',
builder: (BuildContext context, GoRouterState state) {
return const AboutPlaceholderPage();
},
),
],
// Error handling
errorBuilder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Page Not Found'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 16),
Text(
'Page Not Found',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'The page "${state.path}" does not exist.',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go Home'),
),
],
),
),
);
},
// Redirect handler for authentication or onboarding (optional)
redirect: (BuildContext context, GoRouterState state) {
// Add any global redirect logic here
// For example, redirect to onboarding or login if needed
return null; // No global redirect
},
);
/// Placeholder page for settings (for future implementation)
class SettingsPlaceholderPage extends StatelessWidget {
const SettingsPlaceholderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop(),
),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.settings,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'Settings',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Settings page coming soon',
style: TextStyle(
color: Colors.grey,
),
),
],
),
),
);
}
}
/// Placeholder page for about (for future implementation)
class AboutPlaceholderPage extends StatelessWidget {
const AboutPlaceholderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('About'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop(),
),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.info_outline,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'Barcode Scanner App',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Version 1.0.0',
style: TextStyle(
color: Colors.grey,
),
),
],
),
),
);
}
}
/// Extension methods for easier navigation
extension AppRouterExtension on BuildContext {
/// Navigate to home page
void goHome() => go('/');
/// Navigate to detail page with barcode
void goToDetail(String barcode) => go('/detail/$barcode');
/// Navigate to settings
void goToSettings() => go('/settings');
/// Navigate to about page
void goToAbout() => go('/about');
}

View File

@@ -0,0 +1,298 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../constants/app_constants.dart';
/// Application theme configuration using Material Design 3
class AppTheme {
AppTheme._();
// Color scheme for light theme
static const ColorScheme _lightColorScheme = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF1976D2), // Blue
onPrimary: Color(0xFFFFFFFF),
primaryContainer: Color(0xFFE3F2FD),
onPrimaryContainer: Color(0xFF0D47A1),
secondary: Color(0xFF757575), // Grey
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFFE0E0E0),
onSecondaryContainer: Color(0xFF424242),
tertiary: Color(0xFF4CAF50), // Green
onTertiary: Color(0xFFFFFFFF),
tertiaryContainer: Color(0xFFE8F5E8),
onTertiaryContainer: Color(0xFF2E7D32),
error: Color(0xFFD32F2F),
onError: Color(0xFFFFFFFF),
errorContainer: Color(0xFFFFEBEE),
onErrorContainer: Color(0xFFB71C1C),
surface: Color(0xFFFFFFFF),
onSurface: Color(0xFF212121),
background: Color(0xFFF5F5F5),
onBackground: Color(0xFF616161),
onSurfaceVariant: Color(0xFF616161),
outline: Color(0xFFBDBDBD),
outlineVariant: Color(0xFFE0E0E0),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFF303030),
onInverseSurface: Color(0xFFF5F5F5),
inversePrimary: Color(0xFF90CAF9),
surfaceTint: Color(0xFF1976D2),
);
// Color scheme for dark theme
static const ColorScheme _darkColorScheme = ColorScheme(
brightness: Brightness.dark,
primary: Color(0xFF90CAF9), // Light Blue
onPrimary: Color(0xFF0D47A1),
primaryContainer: Color(0xFF1565C0),
onPrimaryContainer: Color(0xFFE3F2FD),
secondary: Color(0xFFBDBDBD), // Light Grey
onSecondary: Color(0xFF424242),
secondaryContainer: Color(0xFF616161),
onSecondaryContainer: Color(0xFFE0E0E0),
tertiary: Color(0xFF81C784), // Light Green
onTertiary: Color(0xFF2E7D32),
tertiaryContainer: Color(0xFF388E3C),
onTertiaryContainer: Color(0xFFE8F5E8),
error: Color(0xFFEF5350),
onError: Color(0xFFB71C1C),
errorContainer: Color(0xFFD32F2F),
onErrorContainer: Color(0xFFFFEBEE),
surface: Color(0xFF121212),
onSurface: Color(0xFFE0E0E0),
background: Color(0xFF2C2C2C),
onBackground: Color(0xFFBDBDBD),
onSurfaceVariant: Color(0xFFBDBDBD),
outline: Color(0xFF757575),
outlineVariant: Color(0xFF424242),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFFE0E0E0),
onInverseSurface: Color(0xFF303030),
inversePrimary: Color(0xFF1976D2),
surfaceTint: Color(0xFF90CAF9),
);
/// Light theme configuration
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
colorScheme: _lightColorScheme,
scaffoldBackgroundColor: _lightColorScheme.surface,
// App Bar Theme
appBarTheme: AppBarTheme(
elevation: 0,
scrolledUnderElevation: 1,
backgroundColor: _lightColorScheme.surface,
foregroundColor: _lightColorScheme.onSurface,
titleTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: _lightColorScheme.onSurface,
),
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
// Elevated Button Theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 0,
minimumSize: const Size(double.infinity, AppConstants.buttonHeight),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
// Text Button Theme
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
minimumSize: const Size(double.infinity, AppConstants.buttonHeight),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
// Input Decoration Theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: _lightColorScheme.background,
contentPadding: const EdgeInsets.all(AppConstants.defaultPadding),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _lightColorScheme.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _lightColorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _lightColorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _lightColorScheme.error),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _lightColorScheme.error, width: 2),
),
labelStyle: TextStyle(color: _lightColorScheme.onSurfaceVariant),
hintStyle: TextStyle(color: _lightColorScheme.onSurfaceVariant),
),
// List Tile Theme
listTileTheme: const ListTileThemeData(
contentPadding: EdgeInsets.symmetric(
horizontal: AppConstants.defaultPadding,
vertical: AppConstants.smallPadding,
),
),
// Divider Theme
dividerTheme: DividerThemeData(
color: _lightColorScheme.outline,
thickness: 0.5,
),
// Progress Indicator Theme
progressIndicatorTheme: ProgressIndicatorThemeData(
color: _lightColorScheme.primary,
),
// Snack Bar Theme
snackBarTheme: SnackBarThemeData(
backgroundColor: _lightColorScheme.inverseSurface,
contentTextStyle: TextStyle(color: _lightColorScheme.onInverseSurface),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
behavior: SnackBarBehavior.floating,
),
);
}
/// Dark theme configuration
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
colorScheme: _darkColorScheme,
scaffoldBackgroundColor: _darkColorScheme.surface,
// App Bar Theme
appBarTheme: AppBarTheme(
elevation: 0,
scrolledUnderElevation: 1,
backgroundColor: _darkColorScheme.surface,
foregroundColor: _darkColorScheme.onSurface,
titleTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: _darkColorScheme.onSurface,
),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
// Elevated Button Theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 0,
minimumSize: const Size(double.infinity, AppConstants.buttonHeight),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
// Text Button Theme
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
minimumSize: const Size(double.infinity, AppConstants.buttonHeight),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
// Input Decoration Theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: _darkColorScheme.background,
contentPadding: const EdgeInsets.all(AppConstants.defaultPadding),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _darkColorScheme.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _darkColorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _darkColorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _darkColorScheme.error),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
borderSide: BorderSide(color: _darkColorScheme.error, width: 2),
),
labelStyle: TextStyle(color: _darkColorScheme.onSurfaceVariant),
hintStyle: TextStyle(color: _darkColorScheme.onSurfaceVariant),
),
// List Tile Theme
listTileTheme: const ListTileThemeData(
contentPadding: EdgeInsets.symmetric(
horizontal: AppConstants.defaultPadding,
vertical: AppConstants.smallPadding,
),
),
// Divider Theme
dividerTheme: DividerThemeData(
color: _darkColorScheme.outline,
thickness: 0.5,
),
// Progress Indicator Theme
progressIndicatorTheme: ProgressIndicatorThemeData(
color: _darkColorScheme.primary,
),
// Snack Bar Theme
snackBarTheme: SnackBarThemeData(
backgroundColor: _darkColorScheme.inverseSurface,
contentTextStyle: TextStyle(color: _darkColorScheme.onInverseSurface),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
behavior: SnackBarBehavior.floating,
),
);
}
}