feat: implement AI export service and establish project navigation and theming infrastructure + acces internet
Build & Release Android APK / build-apk (push) Successful in 23m34s
Build & Release Android APK / build-apk (push) Successful in 23m34s
This commit is contained in:
+375
-40
@@ -1,89 +1,424 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Définition des couleurs d'accent disponibles pour la personnalisation.
|
||||
class AppAccentColor {
|
||||
final String id;
|
||||
final String name;
|
||||
final Color color;
|
||||
final Color lightColor;
|
||||
final Color darkColor;
|
||||
final IconData icon;
|
||||
|
||||
const AppAccentColor({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.color,
|
||||
required this.lightColor,
|
||||
required this.darkColor,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
static const AppAccentColor blue = AppAccentColor(
|
||||
id: 'blue',
|
||||
name: 'Bleu Cobalt',
|
||||
color: Color(0xFF2563EB),
|
||||
lightColor: Color(0xFF60A5FA),
|
||||
darkColor: Color(0xFF1D4ED8),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const AppAccentColor red = AppAccentColor(
|
||||
id: 'red',
|
||||
name: 'Rouge Cible',
|
||||
color: Color(0xFFDC2626),
|
||||
lightColor: Color(0xFFF87171),
|
||||
darkColor: Color(0xFFB91C1C),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const AppAccentColor green = AppAccentColor(
|
||||
id: 'green',
|
||||
name: 'Vert Viseur',
|
||||
color: Color(0xFF10B981),
|
||||
lightColor: Color(0xFF34D399),
|
||||
darkColor: Color(0xFF059669),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const AppAccentColor orange = AppAccentColor(
|
||||
id: 'orange',
|
||||
name: 'Orange Ambre',
|
||||
color: Color(0xFFFF6D00),
|
||||
lightColor: Color(0xFFFF9E40),
|
||||
darkColor: Color(0xFFD84315),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const AppAccentColor purple = AppAccentColor(
|
||||
id: 'purple',
|
||||
name: 'Violet Cyber',
|
||||
color: Color(0xFF8B5CF6),
|
||||
lightColor: Color(0xFFA78BFA),
|
||||
darkColor: Color(0xFF6D28D9),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const AppAccentColor cyan = AppAccentColor(
|
||||
id: 'cyan',
|
||||
name: 'Cyan Néon',
|
||||
color: Color(0xFF06B6D4),
|
||||
lightColor: Color(0xFF67E8F9),
|
||||
darkColor: Color(0xFF0E7490),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const AppAccentColor gold = AppAccentColor(
|
||||
id: 'gold',
|
||||
name: 'Or Compétition',
|
||||
color: Color(0xFFF59E0B),
|
||||
lightColor: Color(0xFFFBBF24),
|
||||
darkColor: Color(0xFFD97706),
|
||||
icon: Icons.lens,
|
||||
);
|
||||
|
||||
static const List<AppAccentColor> allAccents = [
|
||||
blue,
|
||||
red,
|
||||
green,
|
||||
orange,
|
||||
purple,
|
||||
cyan,
|
||||
gold,
|
||||
];
|
||||
|
||||
static AppAccentColor fromId(String? id) {
|
||||
return allAccents.firstWhere(
|
||||
(a) => a.id == id,
|
||||
orElse: () => blue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Système de design et thème de l'application Bully.
|
||||
class AppTheme {
|
||||
AppTheme._();
|
||||
|
||||
static const Color primaryColor = Color(0xFF1E88E5);
|
||||
static const Color secondaryColor = Color(0xFF43A047);
|
||||
static const Color errorColor = Color(0xFFE53935);
|
||||
static const Color warningColor = Color(0xFFFFA726);
|
||||
static const Color successColor = Color(0xFF66BB6A);
|
||||
// Accents par défaut (compatibilité statique)
|
||||
static const Color primaryColor = Color(0xFF2563EB);
|
||||
static const Color primaryLight = Color(0xFF60A5FA);
|
||||
static const Color primaryDark = Color(0xFF1D4ED8);
|
||||
|
||||
static const Color backgroundColor = Color(0xFFF5F5F5);
|
||||
static const Color surfaceColor = Colors.white;
|
||||
static const Color textPrimary = Color(0xFF212121);
|
||||
static const Color textSecondary = Color(0xFF757575);
|
||||
static const Color secondaryColor = Color(0xFF10B981);
|
||||
static const Color secondaryDark = Color(0xFF059669);
|
||||
|
||||
// Impact colors for visualization
|
||||
static const Color impactColor = Color(0xFFFF5722);
|
||||
static const Color accentBlue = Color(0xFF0EA5E9);
|
||||
static const Color accentGold = Color(0xFFFFB300);
|
||||
|
||||
static const Color errorColor = Color(0xFFEF4444);
|
||||
static const Color warningColor = Color(0xFFF59E0B);
|
||||
static const Color successColor = Color(0xFF10B981);
|
||||
|
||||
// Palette Thème Sombre (Standard Stand de Tir)
|
||||
static const Color darkBackground = Color(0xFF0D1219);
|
||||
static const Color darkSurface = Color(0xFF161E28);
|
||||
static const Color darkSurfaceElevated = Color(0xFF1E2836);
|
||||
static const Color darkSurfaceVariant = Color(0xFF253243);
|
||||
static const Color darkBorder = Color(0xFF2C3B4E);
|
||||
static const Color darkBorderLight = Color(0xFF3B4D65);
|
||||
static const Color darkTextPrimary = Color(0xFFF1F5F9);
|
||||
static const Color darkTextSecondary = Color(0xFF94A3B8);
|
||||
static const Color darkTextMuted = Color(0xFF64748B);
|
||||
|
||||
// Palette Thème Clair
|
||||
static const Color lightBackground = Color(0xFFF8FAFC);
|
||||
static const Color lightSurface = Color(0xFFFFFFFF);
|
||||
static const Color lightSurfaceElevated = Color(0xFFFFFFFF);
|
||||
static const Color lightSurfaceVariant = Color(0xFFF1F5F9);
|
||||
static const Color lightBorder = Color(0xFFE2E8F0);
|
||||
static const Color lightTextPrimary = Color(0xFF0F172A);
|
||||
static const Color lightTextSecondary = Color(0xFF64748B);
|
||||
static const Color lightTextMuted = Color(0xFF94A3B8);
|
||||
|
||||
// Compatibilité ascendante
|
||||
static const Color backgroundColor = lightBackground;
|
||||
static const Color surfaceColor = lightSurface;
|
||||
static const Color textPrimary = lightTextPrimary;
|
||||
static const Color textSecondary = lightTextSecondary;
|
||||
|
||||
// Couleurs des impacts pour l'overlay
|
||||
static const Color impactColor = Color(0xFFFF3D00);
|
||||
static const Color impactOutlineColor = Color(0xFFFFFFFF);
|
||||
static const Color groupingCenterColor = Color(0xFF2196F3);
|
||||
static const Color groupingCircleColor = Color(0x4D2196F3);
|
||||
static const Color groupingCenterColor = Color(0xFF00E5FF);
|
||||
static const Color groupingCircleColor = Color(0x4D00E5FF);
|
||||
|
||||
// Score zone colors
|
||||
// Couleurs des zones de score cibles concentriques
|
||||
static const List<Color> zoneColors = [
|
||||
Color(0xFFFFEB3B), // Zone 10 - Gold
|
||||
Color(0xFFFFEB3B), // Zone 9
|
||||
Color(0xFFFFB300), // Zone 10 - Or
|
||||
Color(0xFFFFCA28), // Zone 9
|
||||
Color(0xFFFF5722), // Zone 8
|
||||
Color(0xFFFF5722), // Zone 7
|
||||
Color(0xFF2196F3), // Zone 6
|
||||
Color(0xFF2196F3), // Zone 5
|
||||
Color(0xFF4CAF50), // Zone 4
|
||||
Color(0xFF4CAF50), // Zone 3
|
||||
Color(0xFFFF7043), // Zone 7
|
||||
Color(0xFF29B6F6), // Zone 6
|
||||
Color(0xFF4FC3F7), // Zone 5
|
||||
Color(0xFF66BB6A), // Zone 4
|
||||
Color(0xFF81C784), // Zone 3
|
||||
Color(0xFFFFFFFF), // Zone 2
|
||||
Color(0xFFFFFFFF), // Zone 1
|
||||
Color(0xFFE0E0E0), // Zone 1
|
||||
];
|
||||
|
||||
static ThemeData get lightTheme {
|
||||
static ThemeData get lightTheme => buildLightTheme(AppAccentColor.blue);
|
||||
static ThemeData get darkTheme => buildDarkTheme(AppAccentColor.blue);
|
||||
|
||||
static ThemeData buildLightTheme(AppAccentColor accent) {
|
||||
final activePrimary = accent.color;
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.light,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: activePrimary,
|
||||
secondary: secondaryColor,
|
||||
surface: lightSurface,
|
||||
error: errorColor,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: lightTextPrimary,
|
||||
onError: Colors.white,
|
||||
outline: lightBorder,
|
||||
),
|
||||
scaffoldBackgroundColor: backgroundColor,
|
||||
scaffoldBackgroundColor: lightBackground,
|
||||
cardColor: lightSurface,
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: lightSurface,
|
||||
foregroundColor: lightTextPrimary,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: lightTextPrimary,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: 2,
|
||||
elevation: 0,
|
||||
color: lightSurface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: lightBorder, width: 1),
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
elevation: 0,
|
||||
backgroundColor: activePrimary,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||
backgroundColor: primaryColor,
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: lightTextPrimary,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
side: const BorderSide(color: lightBorder, width: 1.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: lightSurfaceVariant,
|
||||
selectedColor: activePrimary.withValues(alpha: 0.15),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: lightTextPrimary,
|
||||
),
|
||||
secondaryLabelStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: activePrimary,
|
||||
),
|
||||
side: const BorderSide(color: lightBorder, width: 1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: lightSurfaceVariant,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: lightBorder),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: lightBorder),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: activePrimary, width: 1.8),
|
||||
),
|
||||
labelStyle: const TextStyle(color: lightTextSecondary),
|
||||
hintStyle: const TextStyle(color: lightTextMuted),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: lightBorder,
|
||||
thickness: 1,
|
||||
space: 24,
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: activePrimary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 3,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
static ThemeData buildDarkTheme(AppAccentColor accent) {
|
||||
final activePrimary = accent.color;
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.dark,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: activePrimary,
|
||||
secondary: secondaryColor,
|
||||
surface: darkSurface,
|
||||
error: errorColor,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: darkTextPrimary,
|
||||
onError: Colors.white,
|
||||
outline: darkBorder,
|
||||
),
|
||||
scaffoldBackgroundColor: darkBackground,
|
||||
cardColor: darkSurface,
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
backgroundColor: darkBackground,
|
||||
foregroundColor: darkTextPrimary,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: darkTextPrimary,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: 2,
|
||||
elevation: 0,
|
||||
color: darkSurface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: darkBorder, width: 1),
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
backgroundColor: activePrimary,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: darkTextPrimary,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
side: const BorderSide(color: darkBorder, width: 1.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: darkSurfaceElevated,
|
||||
selectedColor: activePrimary.withValues(alpha: 0.2),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: darkTextPrimary,
|
||||
),
|
||||
secondaryLabelStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: activePrimary,
|
||||
),
|
||||
side: const BorderSide(color: darkBorder, width: 1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: darkSurfaceElevated,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: darkBorder),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: darkBorder),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: activePrimary, width: 1.8),
|
||||
),
|
||||
labelStyle: const TextStyle(color: darkTextSecondary),
|
||||
hintStyle: const TextStyle(color: darkTextMuted),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: darkBorder,
|
||||
thickness: 1,
|
||||
space: 24,
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: activePrimary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 3,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,36 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'app_theme.dart';
|
||||
|
||||
class ThemeProvider with ChangeNotifier {
|
||||
static const String _themeModeKey = 'user_theme_mode';
|
||||
|
||||
static const String _accentKey = 'user_accent_color';
|
||||
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
AppAccentColor _accent = AppAccentColor.blue;
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
AppAccentColor get currentAccent => _accent;
|
||||
Color get primaryColor => _accent.color;
|
||||
|
||||
ThemeData get lightTheme => AppTheme.buildLightTheme(_accent);
|
||||
ThemeData get darkTheme => AppTheme.buildDarkTheme(_accent);
|
||||
|
||||
ThemeProvider() {
|
||||
loadThemeMode();
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
Future<void> loadThemeMode() async {
|
||||
Future<void> loadSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final modeIndex = prefs.getInt(_themeModeKey);
|
||||
final accentId = prefs.getString(_accentKey);
|
||||
|
||||
if (modeIndex != null) {
|
||||
_themeMode = ThemeMode.values[modeIndex];
|
||||
notifyListeners();
|
||||
}
|
||||
if (accentId != null) {
|
||||
_accent = AppAccentColor.fromId(accentId);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(ThemeMode mode) async {
|
||||
if (_themeMode == mode) return;
|
||||
|
||||
|
||||
_themeMode = mode;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_themeModeKey, mode.index);
|
||||
}
|
||||
|
||||
Future<void> setAccent(AppAccentColor accent) async {
|
||||
if (_accent.id == accent.id) return;
|
||||
|
||||
_accent = accent;
|
||||
notifyListeners();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_accentKey, accent.id);
|
||||
}
|
||||
|
||||
String get themeModeName {
|
||||
switch (_themeMode) {
|
||||
case ThemeMode.system:
|
||||
|
||||
Reference in New Issue
Block a user