- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
342 lines
10 KiB
Dart
Executable File
342 lines
10 KiB
Dart
Executable File
import 'package:flutter/material.dart';
|
|
import 'package:geosector_app/core/services/theme_service.dart';
|
|
import 'package:geosector_app/presentation/widgets/theme_switcher.dart';
|
|
|
|
/// Page de paramètres pour la gestion du thème
|
|
class ThemeSettingsPage extends StatelessWidget {
|
|
const ThemeSettingsPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Paramètres d\'affichage'),
|
|
backgroundColor: theme.colorScheme.surface,
|
|
foregroundColor: theme.colorScheme.onSurface,
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Section informations
|
|
_buildInfoSection(context),
|
|
const SizedBox(height: 32),
|
|
|
|
// Section sélection du thème
|
|
_buildThemeSection(context),
|
|
const SizedBox(height: 32),
|
|
|
|
// Section aperçu
|
|
_buildPreviewSection(context),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoSection(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.info_outline, color: theme.colorScheme.primary),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'À propos des thèmes',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Text(
|
|
'• Mode Automatique : Suit les préférences de votre système\n'
|
|
'• Mode Clair : Interface claire en permanence\n'
|
|
'• Mode Sombre : Interface sombre en permanence\n\n'
|
|
'Le mode automatique détecte automatiquement si votre appareil '
|
|
'est configuré en mode sombre ou clair et adapte l\'interface en conséquence.',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildThemeSection(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.palette_outlined, color: theme.colorScheme.primary),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Choix du thème',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Thème actuel
|
|
AnimatedBuilder(
|
|
animation: ThemeService.instance,
|
|
builder: (context, child) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.primaryContainer.withOpacity(0.3),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(
|
|
color: theme.colorScheme.primary.withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
ThemeService.instance.themeModeIcon,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Thème actuel',
|
|
style: theme.textTheme.bodySmall,
|
|
),
|
|
Text(
|
|
ThemeService.instance.themeModeDescription,
|
|
style: theme.textTheme.bodyLarge?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Boutons de sélection style segments
|
|
Text(
|
|
'Sélectionner un thème :',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
Center(
|
|
child: ThemeSwitcher(
|
|
style: ThemeSwitcherStyle.segmentedButton,
|
|
onThemeChanged: () {
|
|
// Optionnel: feedback haptic ou autres actions
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
'Thème changé vers ${ThemeService.instance.themeModeDescription}'),
|
|
duration: const Duration(seconds: 2),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Options alternatives
|
|
Text(
|
|
'Autres options :',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// Dropdown
|
|
const Row(
|
|
children: [
|
|
Text('Menu déroulant : '),
|
|
ThemeSwitcher(
|
|
style: ThemeSwitcherStyle.dropdown,
|
|
showLabel: true,
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
// Toggle buttons
|
|
const Row(
|
|
children: [
|
|
Text('Boutons : '),
|
|
ThemeSwitcher(style: ThemeSwitcherStyle.toggleButtons),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPreviewSection(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.preview, color: theme.colorScheme.primary),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Aperçu des couleurs',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Grille de couleurs
|
|
GridView.count(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
crossAxisCount: 4,
|
|
mainAxisSpacing: 8,
|
|
crossAxisSpacing: 8,
|
|
children: [
|
|
_buildColorSample('Primary', theme.colorScheme.primary,
|
|
theme.colorScheme.onPrimary),
|
|
_buildColorSample('Secondary', theme.colorScheme.secondary,
|
|
theme.colorScheme.onSecondary),
|
|
_buildColorSample('Surface', theme.colorScheme.surface,
|
|
theme.colorScheme.onSurface),
|
|
_buildColorSample('Background', theme.colorScheme.surface,
|
|
theme.colorScheme.onSurface),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Exemples de composants
|
|
Text(
|
|
'Exemples de composants :',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () {},
|
|
child: const Text('Bouton'),
|
|
),
|
|
OutlinedButton(
|
|
onPressed: () {},
|
|
child: const Text('Bouton'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('Bouton'),
|
|
),
|
|
const Chip(
|
|
label: Text('Chip'),
|
|
avatar: Icon(Icons.star, size: 16),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildColorSample(String label, Color color, Color onColor) {
|
|
return Container(
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
color: color,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.grey.withOpacity(0.3)),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: onColor,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Dialog simple pour les paramètres de thème
|
|
class ThemeSettingsDialog extends StatelessWidget {
|
|
const ThemeSettingsDialog({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return AlertDialog(
|
|
title: Row(
|
|
children: [
|
|
Icon(Icons.palette_outlined, color: theme.colorScheme.primary),
|
|
const SizedBox(width: 8),
|
|
const Text('Apparence'),
|
|
],
|
|
),
|
|
content: const Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ThemeInfo(),
|
|
SizedBox(height: 16),
|
|
ThemeSwitcher(
|
|
style: ThemeSwitcherStyle.segmentedButton,
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('Fermer'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|