feat: Implements app theme system

Replaces hardcoded colors and standard Theme.of(context) accesses with a custom AppTheme.

Introduces distinct light and dark themes and persists the selected theme mode using shared_preferences.

Integrates the theme into the main app structure and applies it to various components and pages.

Adds google_fonts dependency for theme typography.
This commit is contained in:
mandreshope 2025-06-23 17:48:03 +03:00
parent 94b4fc1723
commit e963abb0ce
16 changed files with 591 additions and 175 deletions

3
devtools_options.yaml Normal file
View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@ -1,30 +1,44 @@
import 'package:barcode_scanner/router/router.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:barcode_scanner/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:responsive_framework/responsive_framework.dart';
class App extends StatelessWidget {
class App extends ConsumerStatefulWidget {
const App({super.key});
@override
ConsumerState<App> createState() => _AppState();
}
class _AppState extends ConsumerState<App> {
ThemeMode _themeMode = AppTheme.themeMode;
void setThemeMode(ThemeMode mode) => safeSetState(() {
_themeMode = mode;
AppTheme.saveThemeMode(mode);
});
@override
Widget build(BuildContext context) {
return ProviderScope(overrides: [routerProvider], child: _InnerApp());
return ProviderScope(
overrides: [routerProvider],
child: _InnerApp(themeMode: _themeMode),
);
}
}
class _InnerApp extends ConsumerWidget {
const _InnerApp();
const _InnerApp({required this.themeMode});
final ThemeMode themeMode;
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: "BarcodeScan",
theme: ThemeData(
fontFamily: 'Roboto',
scaffoldBackgroundColor: Colors.white,
),
locale: Locale('fr'),
supportedLocales: [Locale('fr', 'FR'), Locale('en', 'US')],
localizationsDelegates: [
@ -40,6 +54,9 @@ class _InnerApp extends ConsumerWidget {
const Breakpoint(start: 451, end: 800, name: TABLET),
],
),
theme: ThemeData(brightness: Brightness.light),
darkTheme: ThemeData(brightness: Brightness.dark),
themeMode: themeMode,
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
class LoadingProgressComponent extends StatelessWidget {
@ -22,7 +23,7 @@ class LoadingProgressComponent extends StatelessWidget {
value: value,
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
color ?? Theme.of(context).primaryColor,
color ?? AppTheme.of(context).primary,
),
),
);

View File

@ -1,4 +1,5 @@
import 'package:barcode_scanner/components/loading_progress_component.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
class PrimaryButtonComponent extends StatelessWidget {
@ -16,7 +17,7 @@ class PrimaryButtonComponent extends StatelessWidget {
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
backgroundColor: AppTheme.of(context).primary,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
@ -26,14 +27,16 @@ class PrimaryButtonComponent extends StatelessWidget {
height: 20,
width: 20,
child: Center(
child: const LoadingProgressComponent(color: Colors.white),
child: LoadingProgressComponent(
color: AppTheme.of(context).primary,
),
),
)
: Text(
text,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
color: AppTheme.of(context).primaryText,
),
),
);

View File

@ -1,3 +1,4 @@
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
class ProductScannedComponent extends StatefulWidget {
@ -34,7 +35,7 @@ class _ProductScannedComponentState extends State<ProductScannedComponent> {
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
color: AppTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
@ -146,15 +147,26 @@ class _ProductScannedComponentState extends State<ProductScannedComponent> {
onPressed: () async {
await widget.onRescan?.call();
},
child: Text('Nouveau scan'),
child: Text(
'Nouveau scan',
style: AppTheme.of(context).bodyMedium,
),
),
),
Expanded(
child: FilledButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
AppTheme.of(context).primary,
),
),
onPressed: () {
widget.onDetails?.call();
},
child: Text('Voir détails'),
child: Text(
'Voir détails',
style: AppTheme.of(context).bodyMedium,
),
),
),
],

View File

@ -1,6 +1,7 @@
import 'package:barcode_scanner/pages/login_page/login_page_model.dart';
import 'package:barcode_scanner/router/go_router_builder.dart';
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -17,8 +18,8 @@ class _HomePageState extends ConsumerState<HomePage> {
final primaryColor = Theme.of(context).primaryColor;
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
appBar: AppBar(
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
actions: [
IconButton(
@ -32,7 +33,6 @@ class _HomePageState extends ConsumerState<HomePage> {
),
],
),
backgroundColor: Colors.white,
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
@ -44,7 +44,7 @@ class _HomePageState extends ConsumerState<HomePage> {
width: 100,
height: 100,
decoration: BoxDecoration(
color: primaryColor,
color: AppTheme.of(context).primary,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
@ -87,7 +87,7 @@ class _HomePageState extends ConsumerState<HomePage> {
height: 200,
padding: const EdgeInsets.all(40),
decoration: BoxDecoration(
color: const Color(0xFFF6F8FA),
color: AppTheme.of(context).alternate,
borderRadius: BorderRadius.circular(24),
boxShadow: const [
BoxShadow(
@ -97,10 +97,10 @@ class _HomePageState extends ConsumerState<HomePage> {
),
],
),
child: const Icon(
child: Icon(
Icons.qr_code_2_rounded,
size: 60,
color: Colors.black87,
color: AppTheme.of(context).primaryText,
),
),
@ -122,7 +122,7 @@ class _HomePageState extends ConsumerState<HomePage> {
ScannerRoute().push(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
backgroundColor: AppTheme.of(context).primary,
padding: const EdgeInsets.symmetric(vertical: 18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),

View File

@ -1,8 +1,10 @@
import 'package:barcode_scanner/components/primary_button_component.dart';
import 'package:barcode_scanner/pages/login_page/login_page_model.dart';
import 'package:barcode_scanner/router/go_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@ -32,15 +34,15 @@ class _LoginPageState extends ConsumerState<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
backgroundColor: AppTheme.of(context).primaryBackground,
appBar: AppBar(
title: Text(
'BarcodeScan',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
style: AppTheme.of(context).titleLarge,
),
centerTitle: true,
backgroundColor: Colors.white,
backgroundColor: AppTheme.of(context).primaryBackground,
elevation: 0,
),
body: Align(
@ -58,15 +60,14 @@ class _LoginPageState extends ConsumerState<LoginPage> {
const SizedBox(height: 32),
Text(
'Welcome Back',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
style: AppTheme.of(context).titleLarge,
),
const SizedBox(height: 8),
Text(
'Fill out the information below in order to access your account.',
style: TextStyle(color: Colors.grey[700]),
style: AppTheme.of(context).bodyMedium.override(
color: AppTheme.of(context).secondaryText,
),
),
const SizedBox(height: 24),
@ -75,7 +76,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
decoration: InputDecoration(
labelText: 'Email',
filled: true,
fillColor: Colors.grey[100],
fillColor: AppTheme.of(context).secondaryBackground,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
@ -101,7 +102,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
decoration: InputDecoration(
labelText: 'Password',
filled: true,
fillColor: Colors.grey[100],
fillColor: AppTheme.of(context).secondaryBackground,
suffixIcon: IconButton(
onPressed: () {
setState(() {
@ -197,7 +198,12 @@ class _LoginPageState extends ConsumerState<LoginPage> {
/// Forgot Password
TextButton(
onPressed: () {},
child: const Text('Forgot Password?'),
child: Text(
'Forgot Password?',
style: TextStyle(
color: AppTheme.of(context).primaryText,
),
),
),
const SizedBox(height: 24),

View File

@ -1,4 +1,6 @@
import 'package:barcode_scanner/components/primary_button_component.dart';
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
class ProductFormPage extends StatefulWidget {
@ -29,7 +31,7 @@ class _ProductFormPageState extends State<ProductFormPage> {
return InputDecoration(
labelText: label,
filled: true,
fillColor: Colors.grey[100],
fillColor: AppTheme.of(context).secondaryBackground,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
@ -40,18 +42,24 @@ class _ProductFormPageState extends State<ProductFormPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
icon: Icon(
Icons.arrow_back_ios,
color: AppTheme.of(context).primaryText,
),
onPressed: () {
Navigator.of(context).pop();
},
),
title: const Text('Formulaire Produit'),
title: Text(
'Formulaire Produit',
style: AppTheme.of(context).titleLarge,
),
centerTitle: true,
backgroundColor: Colors.white,
backgroundColor: AppTheme.of(context).primaryBackground,
elevation: 0,
foregroundColor: Colors.black,
),
body: Padding(
padding: const EdgeInsets.all(24),
@ -96,7 +104,7 @@ class _ProductFormPageState extends State<ProductFormPage> {
const SizedBox(height: 24),
/// Bouton sauvegarder
ElevatedButton(
PrimaryButtonComponent(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Traitement ici
@ -104,20 +112,7 @@ class _ProductFormPageState extends State<ProductFormPage> {
HomeRoute().go(context);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'Sauvegarder',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
text: 'Sauvegarder',
),
],
),

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:barcode_scanner/backend/api/api_calls.dart';
import 'package:barcode_scanner/components/product_scanned_component.dart';
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
@ -226,15 +227,19 @@ class _ScannerPageState extends ConsumerState<ScannerPage>
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
backgroundColor: AppTheme.of(context).primaryBackground,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Colors.white, size: 24.0),
icon: Icon(
Icons.arrow_back_ios,
color: AppTheme.of(context).primaryText,
size: 24.0,
),
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text('Scanner'),
title: Text('Scanner', style: AppTheme.of(context).titleLarge),
actions: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(8.0, 0.0, 8.0, 0.0),
@ -275,9 +280,9 @@ class _ScannerPageState extends ConsumerState<ScannerPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xB7000000),
AppTheme.of(context).primaryBackground,
Colors.transparent,
Color(0xB7000000),
AppTheme.of(context).primaryBackground,
],
stops: [0.0, 0.5, 1.0],
begin: AlignmentDirectional(0.0, -1.0),
@ -304,7 +309,9 @@ class _ScannerPageState extends ConsumerState<ScannerPage>
Text(
'Scanner le code',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
style: TextStyle(
color: AppTheme.of(context).primaryText,
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
@ -316,7 +323,9 @@ class _ScannerPageState extends ConsumerState<ScannerPage>
child: Text(
'Positionnez le code-barres ou QR code dans le cadre',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
style: TextStyle(
color: AppTheme.of(context).primaryText,
),
),
),
],
@ -356,7 +365,9 @@ class _ScannerPageState extends ConsumerState<ScannerPage>
children: [
Icon(
Icons.qr_code_scanner,
color: Colors.white,
color: Colors.white.withValues(
alpha: .5,
),
size: 64.0,
),
],
@ -367,122 +378,24 @@ class _ScannerPageState extends ConsumerState<ScannerPage>
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
24.0,
0.0,
24.0,
40.0,
),
child: Column(
padding: const EdgeInsets.all(40),
child: Row(
mainAxisSize: MainAxisSize.max,
spacing: 12,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Types de codes supportés:',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Container(
width: 80.0,
height: 40.0,
decoration: BoxDecoration(
color: Color(0x33FFFFFF),
borderRadius: BorderRadius.circular(
8.0,
),
),
child: Align(
alignment: AlignmentDirectional(
0.0,
0.0,
),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'EAN-13',
style: TextStyle(
color: Colors.white,
),
),
),
),
),
Container(
width: 80.0,
height: 40.0,
decoration: BoxDecoration(
color: Color(0x33FFFFFF),
borderRadius: BorderRadius.circular(
8.0,
),
),
child: Align(
alignment: AlignmentDirectional(
0.0,
0.0,
),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Code 128',
style: TextStyle(
color: Colors.white,
),
),
),
),
),
Container(
width: 80.0,
height: 40.0,
decoration: BoxDecoration(
color: Color(0x33FFFFFF),
borderRadius: BorderRadius.circular(
8.0,
),
),
child: Align(
alignment: AlignmentDirectional(
0.0,
0.0,
),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'QR Code',
style: TextStyle(
color: Colors.white,
),
),
),
),
),
],
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
color: Color(0x33FFFFFF),
icon: Icon(
mobileScannerController.torchEnabled
? Icons.flash_off
: Icons.flash_on,
color: Colors.white,
size: 28.0,
),
onPressed: () {
mobileScannerController.toggleTorch();
},
),
],
IconButton(
color: AppTheme.of(context).primaryText,
iconSize: 2,
icon: Icon(
mobileScannerController.torchEnabled
? Icons.flash_off
: Icons.flash_on,
color: AppTheme.of(context).primaryText,
size: 28.0,
),
onPressed: () {
mobileScannerController.toggleTorch();
},
),
],
),

View File

@ -2,6 +2,7 @@ import 'package:barcode_scanner/components/loading_progress_component.dart';
import 'package:barcode_scanner/pages/login_page/login_page_model.dart';
import 'package:barcode_scanner/router/go_router_builder.dart';
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -22,6 +23,9 @@ class SplashPage extends ConsumerWidget {
});
}
});
return Scaffold(body: Center(child: LoadingProgressComponent()));
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
body: Center(child: LoadingProgressComponent()),
);
}
}

View File

@ -1,10 +1,12 @@
import 'package:barcode_scanner/pages/login_page/login_page_model.dart';
import 'package:barcode_scanner/provider_container.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/foundation.dart';
class InitializerService {
static Future init() async {
await AppTheme.initialize();
await providerContainer
.read(loginPageModelProvider.notifier)
.checkHasUserConnected();

381
lib/themes/app_theme.dart Normal file
View File

@ -0,0 +1,381 @@
// ignore_for_file: overridden_fields, annotate_overrides
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shared_preferences/shared_preferences.dart';
const kThemeModeKey = '__theme_mode__';
SharedPreferences? _prefs;
abstract class AppTheme {
static Future initialize() async =>
_prefs = await SharedPreferences.getInstance();
static ThemeMode get themeMode {
final darkMode = _prefs?.getBool(kThemeModeKey);
return darkMode == null
? ThemeMode.system
: darkMode
? ThemeMode.dark
: ThemeMode.light;
}
static void saveThemeMode(ThemeMode mode) => mode == ThemeMode.system
? _prefs?.remove(kThemeModeKey)
: _prefs?.setBool(kThemeModeKey, mode == ThemeMode.dark);
static AppTheme of(BuildContext context) {
return Theme.of(context).brightness == Brightness.dark
? DarkModeTheme()
: LightModeTheme();
}
late Color primary;
late Color secondary;
late Color tertiary;
late Color alternate;
late Color primaryText;
late Color secondaryText;
late Color primaryBackground;
late Color secondaryBackground;
late Color accent1;
late Color accent2;
late Color accent3;
late Color accent4;
late Color success;
late Color warning;
late Color error;
late Color info;
late Color trueBlue;
late Color orangePeel;
late Color white;
late Color columbiaBlue;
late Color battleshipGray;
late Color backgroundTransparent;
String get displayLargeFamily => typography.displayLargeFamily;
bool get displayLargeIsCustom => typography.displayLargeIsCustom;
TextStyle get displayLarge => typography.displayLarge;
String get displayMediumFamily => typography.displayMediumFamily;
bool get displayMediumIsCustom => typography.displayMediumIsCustom;
TextStyle get displayMedium => typography.displayMedium;
String get displaySmallFamily => typography.displaySmallFamily;
bool get displaySmallIsCustom => typography.displaySmallIsCustom;
TextStyle get displaySmall => typography.displaySmall;
String get headlineLargeFamily => typography.headlineLargeFamily;
bool get headlineLargeIsCustom => typography.headlineLargeIsCustom;
TextStyle get headlineLarge => typography.headlineLarge;
String get headlineMediumFamily => typography.headlineMediumFamily;
bool get headlineMediumIsCustom => typography.headlineMediumIsCustom;
TextStyle get headlineMedium => typography.headlineMedium;
String get headlineSmallFamily => typography.headlineSmallFamily;
bool get headlineSmallIsCustom => typography.headlineSmallIsCustom;
TextStyle get headlineSmall => typography.headlineSmall;
String get titleLargeFamily => typography.titleLargeFamily;
bool get titleLargeIsCustom => typography.titleLargeIsCustom;
TextStyle get titleLarge => typography.titleLarge;
String get titleMediumFamily => typography.titleMediumFamily;
bool get titleMediumIsCustom => typography.titleMediumIsCustom;
TextStyle get titleMedium => typography.titleMedium;
String get titleSmallFamily => typography.titleSmallFamily;
bool get titleSmallIsCustom => typography.titleSmallIsCustom;
TextStyle get titleSmall => typography.titleSmall;
String get labelLargeFamily => typography.labelLargeFamily;
bool get labelLargeIsCustom => typography.labelLargeIsCustom;
TextStyle get labelLarge => typography.labelLarge;
String get labelMediumFamily => typography.labelMediumFamily;
bool get labelMediumIsCustom => typography.labelMediumIsCustom;
TextStyle get labelMedium => typography.labelMedium;
String get labelSmallFamily => typography.labelSmallFamily;
bool get labelSmallIsCustom => typography.labelSmallIsCustom;
TextStyle get labelSmall => typography.labelSmall;
String get bodyLargeFamily => typography.bodyLargeFamily;
bool get bodyLargeIsCustom => typography.bodyLargeIsCustom;
TextStyle get bodyLarge => typography.bodyLarge;
String get bodyMediumFamily => typography.bodyMediumFamily;
bool get bodyMediumIsCustom => typography.bodyMediumIsCustom;
TextStyle get bodyMedium => typography.bodyMedium;
String get bodySmallFamily => typography.bodySmallFamily;
bool get bodySmallIsCustom => typography.bodySmallIsCustom;
TextStyle get bodySmall => typography.bodySmall;
Typography get typography => ThemeTypography(this);
}
class LightModeTheme extends AppTheme {
@Deprecated('Use primary instead')
Color get primaryColor => primary;
@Deprecated('Use secondary instead')
Color get secondaryColor => secondary;
@Deprecated('Use tertiary instead')
Color get tertiaryColor => tertiary;
late Color primary = const Color(0xFF3D6BB2);
late Color secondary = const Color(0xFFF9A434);
late Color tertiary = const Color(0xFFEE8B60);
late Color alternate = const Color(0xFFE0E3E7);
late Color primaryText = const Color(0xFF14181B);
late Color secondaryText = const Color(0xFF919191);
late Color primaryBackground = const Color(0xFFFFFFFF);
late Color secondaryBackground = const Color(0xFFF1F4F8);
late Color accent1 = const Color(0xFF003EA2);
late Color accent2 = const Color(0xFFB36600);
late Color accent3 = const Color(0x4DEE8B60);
late Color accent4 = const Color(0xCCFFFFFF);
late Color success = const Color(0xFF249689);
late Color warning = const Color(0xFFF9CF58);
late Color error = const Color(0xFFFF5963);
late Color info = const Color(0xFFFFFFFF);
late Color trueBlue = const Color(0xFF3D6BB2);
late Color orangePeel = const Color(0xFFF9A434);
late Color white = const Color(0xFFFFFFFF);
late Color columbiaBlue = const Color(0xFFC8D6E4);
late Color battleshipGray = const Color(0xFF919191);
late Color backgroundTransparent = const Color(0xB9FFFFFF);
}
abstract class Typography {
String get displayLargeFamily;
bool get displayLargeIsCustom;
TextStyle get displayLarge;
String get displayMediumFamily;
bool get displayMediumIsCustom;
TextStyle get displayMedium;
String get displaySmallFamily;
bool get displaySmallIsCustom;
TextStyle get displaySmall;
String get headlineLargeFamily;
bool get headlineLargeIsCustom;
TextStyle get headlineLarge;
String get headlineMediumFamily;
bool get headlineMediumIsCustom;
TextStyle get headlineMedium;
String get headlineSmallFamily;
bool get headlineSmallIsCustom;
TextStyle get headlineSmall;
String get titleLargeFamily;
bool get titleLargeIsCustom;
TextStyle get titleLarge;
String get titleMediumFamily;
bool get titleMediumIsCustom;
TextStyle get titleMedium;
String get titleSmallFamily;
bool get titleSmallIsCustom;
TextStyle get titleSmall;
String get labelLargeFamily;
bool get labelLargeIsCustom;
TextStyle get labelLarge;
String get labelMediumFamily;
bool get labelMediumIsCustom;
TextStyle get labelMedium;
String get labelSmallFamily;
bool get labelSmallIsCustom;
TextStyle get labelSmall;
String get bodyLargeFamily;
bool get bodyLargeIsCustom;
TextStyle get bodyLarge;
String get bodyMediumFamily;
bool get bodyMediumIsCustom;
TextStyle get bodyMedium;
String get bodySmallFamily;
bool get bodySmallIsCustom;
TextStyle get bodySmall;
}
class ThemeTypography extends Typography {
ThemeTypography(this.theme);
final AppTheme theme;
String get displayLargeFamily => 'Poppins';
bool get displayLargeIsCustom => false;
TextStyle get displayLarge => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 64.0,
);
String get displayMediumFamily => 'Poppins';
bool get displayMediumIsCustom => false;
TextStyle get displayMedium => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 44.0,
);
String get displaySmallFamily => 'Poppins';
bool get displaySmallIsCustom => false;
TextStyle get displaySmall => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 36.0,
);
String get headlineLargeFamily => 'Poppins';
bool get headlineLargeIsCustom => false;
TextStyle get headlineLarge => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 32.0,
);
String get headlineMediumFamily => 'Poppins';
bool get headlineMediumIsCustom => false;
TextStyle get headlineMedium => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 28.0,
);
String get headlineSmallFamily => 'Poppins';
bool get headlineSmallIsCustom => false;
TextStyle get headlineSmall => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 24.0,
);
String get titleLargeFamily => 'Poppins';
bool get titleLargeIsCustom => false;
TextStyle get titleLarge => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 20.0,
);
String get titleMediumFamily => 'Poppins';
bool get titleMediumIsCustom => false;
TextStyle get titleMedium => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 18.0,
);
String get titleSmallFamily => 'Poppins';
bool get titleSmallIsCustom => false;
TextStyle get titleSmall => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.w600,
fontSize: 16.0,
);
String get labelLargeFamily => 'Poppins';
bool get labelLargeIsCustom => false;
TextStyle get labelLarge => GoogleFonts.poppins(
color: theme.secondaryText,
fontWeight: FontWeight.normal,
fontSize: 16.0,
);
String get labelMediumFamily => 'Poppins';
bool get labelMediumIsCustom => false;
TextStyle get labelMedium => GoogleFonts.poppins(
color: theme.secondaryText,
fontWeight: FontWeight.normal,
fontSize: 14.0,
);
String get labelSmallFamily => 'Poppins';
bool get labelSmallIsCustom => false;
TextStyle get labelSmall => GoogleFonts.poppins(
color: theme.secondaryText,
fontWeight: FontWeight.normal,
fontSize: 12.0,
);
String get bodyLargeFamily => 'Poppins';
bool get bodyLargeIsCustom => false;
TextStyle get bodyLarge => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.normal,
fontSize: 16.0,
);
String get bodyMediumFamily => 'Poppins';
bool get bodyMediumIsCustom => false;
TextStyle get bodyMedium => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.normal,
fontSize: 14.0,
);
String get bodySmallFamily => 'Poppins';
bool get bodySmallIsCustom => false;
TextStyle get bodySmall => GoogleFonts.poppins(
color: theme.primaryText,
fontWeight: FontWeight.normal,
fontSize: 12.0,
);
}
class DarkModeTheme extends AppTheme {
@Deprecated('Use primary instead')
Color get primaryColor => primary;
@Deprecated('Use secondary instead')
Color get secondaryColor => secondary;
@Deprecated('Use tertiary instead')
Color get tertiaryColor => tertiary;
late Color primary = const Color(0xFF3D6BB2);
late Color secondary = const Color(0xFFB36600);
late Color tertiary = const Color(0xFFEE8B60);
late Color alternate = const Color(0xFF262D34);
late Color primaryText = const Color(0xFFFFFFFF);
late Color secondaryText = const Color(0xFF919191);
late Color primaryBackground = const Color(0xFF14181B);
late Color secondaryBackground = const Color(0xFF1D2428);
late Color accent1 = const Color(0xFF003EA2);
late Color accent2 = const Color(0xFFB36600);
late Color accent3 = const Color(0x4DEE8B60);
late Color accent4 = const Color(0xB2262D34);
late Color success = const Color(0xFF249689);
late Color warning = const Color(0xFFF9CF58);
late Color error = const Color(0xFFFF5963);
late Color info = const Color(0xFFFFFFFF);
late Color trueBlue = const Color(0xFF3D6BB2);
late Color orangePeel = const Color(0xFFF9A434);
late Color white = const Color(0xFFFFFFFF);
late Color columbiaBlue = const Color(0xFFC8D6E4);
late Color battleshipGray = const Color(0xFF919191);
late Color backgroundTransparent = const Color(0x91000000);
}
extension TextStyleHelper on TextStyle {
TextStyle override({
TextStyle? font,
String? fontFamily,
Color? color,
double? fontSize,
FontWeight? fontWeight,
double? letterSpacing,
FontStyle? fontStyle,
bool useGoogleFonts = false,
TextDecoration? decoration,
double? lineHeight,
List<Shadow>? shadows,
String? package,
}) {
if (useGoogleFonts && fontFamily != null) {
font = GoogleFonts.getFont(
fontFamily,
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
);
}
return font != null
? font.copyWith(
color: color ?? this.color,
fontSize: fontSize ?? this.fontSize,
letterSpacing: letterSpacing ?? this.letterSpacing,
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
decoration: decoration,
height: lineHeight,
shadows: shadows,
)
: copyWith(
fontFamily: fontFamily,
package: package,
color: color,
fontSize: fontSize,
letterSpacing: letterSpacing,
fontWeight: fontWeight,
fontStyle: fontStyle,
decoration: decoration,
height: lineHeight,
shadows: shadows,
);
}
}

11
lib/utils/utils.dart Normal file
View File

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
extension StatefulWidgetExtensions on State<StatefulWidget> {
/// Check if the widget exist before safely setting state.
void safeSetState(VoidCallback fn) {
if (mounted) {
// ignore: invalid_use_of_protected_member
setState(fn);
}
}
}

View File

@ -9,10 +9,12 @@ import connectivity_plus
import flutter_secure_storage_macos
import mobile_scanner
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@ -405,6 +405,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
graphs:
dependency: transitive
description:
@ -709,6 +717,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:

View File

@ -48,6 +48,8 @@ dependencies:
connectivity_plus: ^6.1.4
responsive_framework: ^1.5.1
flutter_secure_storage: ^9.2.4
google_fonts: ^6.2.1
shared_preferences: ^2.5.3
dev_dependencies:
flutter_test: