diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -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: diff --git a/lib/app.dart b/lib/app.dart index 10af293..136c1bf 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -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 createState() => _AppState(); +} + +class _AppState extends ConsumerState { + 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, ); } } diff --git a/lib/components/loading_progress_component.dart b/lib/components/loading_progress_component.dart index 28a336d..faf70b4 100644 --- a/lib/components/loading_progress_component.dart +++ b/lib/components/loading_progress_component.dart @@ -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 ?? Theme.of(context).primaryColor, + color ?? AppTheme.of(context).primary, ), ), ); diff --git a/lib/components/primary_button_component.dart b/lib/components/primary_button_component.dart index 88b7df5..d2787ce 100644 --- a/lib/components/primary_button_component.dart +++ b/lib/components/primary_button_component.dart @@ -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, ), ), ); diff --git a/lib/components/product_scanned_component.dart b/lib/components/product_scanned_component.dart index b5aa615..4ab7a80 100644 --- a/lib/components/product_scanned_component.dart +++ b/lib/components/product_scanned_component.dart @@ -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 { 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 { 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, + ), ), ), ], diff --git a/lib/pages/home_page/home_page.dart b/lib/pages/home_page/home_page.dart index b8f3c2a..37d9036 100644 --- a/lib/pages/home_page/home_page.dart +++ b/lib/pages/home_page/home_page.dart @@ -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 { 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 { ), ], ), - backgroundColor: Colors.white, body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), @@ -44,7 +44,7 @@ class _HomePageState extends ConsumerState { 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 { 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 { ), ], ), - 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 { 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), diff --git a/lib/pages/login_page/login_page.dart b/lib/pages/login_page/login_page.dart index 51ddd78..705484e 100644 --- a/lib/pages/login_page/login_page.dart +++ b/lib/pages/login_page/login_page.dart @@ -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 { @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 { 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 { 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 { 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 { /// Forgot Password TextButton( onPressed: () {}, - child: const Text('Forgot Password?'), + child: Text( + 'Forgot Password?', + style: TextStyle( + color: AppTheme.of(context).primaryText, + ), + ), ), const SizedBox(height: 24), diff --git a/lib/pages/product_form_page/product_form_page.dart b/lib/pages/product_form_page/product_form_page.dart index 1c69cf3..94fd64d 100644 --- a/lib/pages/product_form_page/product_form_page.dart +++ b/lib/pages/product_form_page/product_form_page.dart @@ -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 { 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 { @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 { const SizedBox(height: 24), /// Bouton sauvegarder - ElevatedButton( + PrimaryButtonComponent( onPressed: () { if (_formKey.currentState!.validate()) { // Traitement ici @@ -104,20 +112,7 @@ class _ProductFormPageState extends State { 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', ), ], ), diff --git a/lib/pages/scanner_page/scanner_page.dart b/lib/pages/scanner_page/scanner_page.dart index 29d266b..f9e987e 100644 --- a/lib/pages/scanner_page/scanner_page.dart +++ b/lib/pages/scanner_page/scanner_page.dart @@ -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 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 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 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 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 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 ), ), 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(); + }, ), ], ), diff --git a/lib/pages/splash_page/splash_page.dart b/lib/pages/splash_page/splash_page.dart index d906d73..6ff6219 100644 --- a/lib/pages/splash_page/splash_page.dart +++ b/lib/pages/splash_page/splash_page.dart @@ -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()), + ); } } diff --git a/lib/services/service_initializer.dart b/lib/services/service_initializer.dart index 441299c..bc83a62 100644 --- a/lib/services/service_initializer.dart +++ b/lib/services/service_initializer.dart @@ -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(); diff --git a/lib/themes/app_theme.dart b/lib/themes/app_theme.dart new file mode 100644 index 0000000..b098e4e --- /dev/null +++ b/lib/themes/app_theme.dart @@ -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? 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, + ); + } +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 0000000..57f9822 --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +extension StatefulWidgetExtensions on State { + /// Check if the widget exist before safely setting state. + void safeSetState(VoidCallback fn) { + if (mounted) { + // ignore: invalid_use_of_protected_member + setState(fn); + } + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3159819..34ee458 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/pubspec.lock b/pubspec.lock index a1aa167..3bfe5d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 66f740b..3336d4b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: