From 749314d3125bfad16d94304794056f282b4cd25e Mon Sep 17 00:00:00 2001 From: Maxiwere45 Date: Tue, 23 Jun 2026 11:51:15 +0200 Subject: [PATCH] refactor(system): extract system_page sub-widgets into separate files (<150 lines) Move SystemHeader, SectionTitle, StatsGrid/StatItem and PalettePicker into widgets/system/. system_page.dart 260 -> 80 lines. Co-Authored-By: Claude Opus 4.8 --- lib/presentation/pages/system_page.dart | 208 ++---------------- .../widgets/system/palette_picker.dart | 72 ++++++ .../widgets/system/section_title.dart | 22 ++ .../widgets/system/system_header.dart | 24 ++ .../widgets/system/system_stats.dart | 63 ++++++ 5 files changed, 195 insertions(+), 194 deletions(-) create mode 100644 lib/presentation/widgets/system/palette_picker.dart create mode 100644 lib/presentation/widgets/system/section_title.dart create mode 100644 lib/presentation/widgets/system/system_header.dart create mode 100644 lib/presentation/widgets/system/system_stats.dart diff --git a/lib/presentation/pages/system_page.dart b/lib/presentation/pages/system_page.dart index 7a279e7..50985ac 100644 --- a/lib/presentation/pages/system_page.dart +++ b/lib/presentation/pages/system_page.dart @@ -4,7 +4,12 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../core/config/app_constants.dart'; import '../providers/pokedex_provider.dart'; import '../providers/theme_provider.dart'; +import '../widgets/system/system_header.dart'; +import '../widgets/system/section_title.dart'; +import '../widgets/system/system_stats.dart'; +import '../widgets/system/palette_picker.dart'; +/// Page "Système" : statistiques de jeu et sélection de la palette de couleurs. class SystemPage extends ConsumerWidget { const SystemPage({super.key}); @@ -23,14 +28,14 @@ class SystemPage extends ConsumerWidget { color: const Color(0xFFC8D1D8), child: Column( children: [ - _Header(primaryColor: palette.primary), + SystemHeader(primaryColor: palette.primary), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _SectionTitle(text: 'STATISTIQUES', color: palette.primary), + SectionTitle(text: 'STATISTIQUES', color: palette.primary), const SizedBox(height: 8), pokedexAsync.when( loading: () => const Center(child: CircularProgressIndicator()), @@ -44,13 +49,13 @@ class SystemPage extends ConsumerWidget { future: _loadBestScore(), builder: (context, snap) { final best = snap.data ?? 0; - return _StatsGrid( + return StatsGrid( primaryColor: palette.primary, items: [ - _StatItem(label: 'Meilleur score', value: '$best', icon: Icons.emoji_events), - _StatItem(label: 'Attrapés', value: '$caught / $total', icon: Icons.catching_pokemon), - _StatItem(label: 'Vus', value: '$seen / $total', icon: Icons.visibility), - _StatItem(label: 'Complétion', value: '$pct%', icon: Icons.pie_chart), + StatItem(label: 'Meilleur score', value: '$best', icon: Icons.emoji_events), + StatItem(label: 'Attrapés', value: '$caught / $total', icon: Icons.catching_pokemon), + StatItem(label: 'Vus', value: '$seen / $total', icon: Icons.visibility), + StatItem(label: 'Complétion', value: '$pct%', icon: Icons.pie_chart), ], ); }, @@ -58,9 +63,9 @@ class SystemPage extends ConsumerWidget { }, ), const SizedBox(height: 24), - _SectionTitle(text: 'PALETTE DE COULEURS', color: palette.primary), + SectionTitle(text: 'PALETTE DE COULEURS', color: palette.primary), const SizedBox(height: 8), - _PalettePicker( + PalettePicker( selectedIndex: paletteIndex, onSelect: (i) => ref.read(themeProvider.notifier).setPalette(i), ), @@ -73,188 +78,3 @@ class SystemPage extends ConsumerWidget { ); } } - -class _Header extends StatelessWidget { - final Color primaryColor; - const _Header({required this.primaryColor}); - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: primaryColor, - border: Border(bottom: BorderSide(color: primaryColor.withAlpha(180), width: 3)), - ), - child: const Text( - 'SYSTÈME', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 28, - fontWeight: FontWeight.bold, - letterSpacing: 4, - ), - ), - ); - } -} - -class _SectionTitle extends StatelessWidget { - final String text; - final Color color; - const _SectionTitle({required this.text, required this.color}); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Container(width: 4, height: 20, color: color), - const SizedBox(width: 8), - Text( - text, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: color, - letterSpacing: 2, - ), - ), - ], - ); - } -} - -class _StatItem { - final String label; - final String value; - final IconData icon; - const _StatItem({required this.label, required this.value, required this.icon}); -} - -class _StatsGrid extends StatelessWidget { - final Color primaryColor; - final List<_StatItem> items; - const _StatsGrid({required this.primaryColor, required this.items}); - - @override - Widget build(BuildContext context) { - return GridView.count( - crossAxisCount: 2, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 1.4, - children: items.map((item) => _StatCard(item: item, color: primaryColor)).toList(), - ); - } -} - -class _StatCard extends StatelessWidget { - final _StatItem item; - final Color color; - const _StatCard({required this.item, required this.color}); - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: color.withAlpha(80), width: 2), - boxShadow: [ - BoxShadow(color: Colors.black.withAlpha(25), blurRadius: 4, offset: const Offset(0, 2)), - ], - ), - padding: const EdgeInsets.all(12), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(item.icon, color: color, size: 28), - const SizedBox(height: 6), - Text( - item.value, - style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color), - ), - Text( - item.label, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 12, color: Colors.black54), - ), - ], - ), - ); - } -} - -class _PalettePicker extends StatelessWidget { - final int selectedIndex; - final void Function(int) onSelect; - const _PalettePicker({required this.selectedIndex, required this.onSelect}); - - @override - Widget build(BuildContext context) { - return Column( - children: List.generate(appPalettes.length, (i) { - final p = appPalettes[i]; - final isSelected = i == selectedIndex; - return GestureDetector( - onTap: () => onSelect(i), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - margin: const EdgeInsets.only(bottom: 10), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: isSelected ? p.primary : Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isSelected ? p.primary : Colors.grey.shade300, - width: isSelected ? 3 : 1.5, - ), - boxShadow: isSelected - ? [BoxShadow(color: p.primary.withAlpha(80), blurRadius: 8, offset: const Offset(0, 3))] - : [], - ), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: p.primary, - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2), - ), - ), - const SizedBox(width: 8), - Container( - width: 20, - height: 20, - decoration: BoxDecoration( - color: p.surface, - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 1.5), - ), - ), - const SizedBox(width: 16), - Text( - p.name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: isSelected ? Colors.white : Colors.black87, - ), - ), - const Spacer(), - if (isSelected) - const Icon(Icons.check_circle, color: Colors.white, size: 22), - ], - ), - ), - ); - }), - ); - } -} diff --git a/lib/presentation/widgets/system/palette_picker.dart b/lib/presentation/widgets/system/palette_picker.dart new file mode 100644 index 0000000..bf5d103 --- /dev/null +++ b/lib/presentation/widgets/system/palette_picker.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import '../../providers/theme_provider.dart'; + +/// Sélecteur de palette de couleurs de l'application. +class PalettePicker extends StatelessWidget { + final int selectedIndex; + final void Function(int) onSelect; + const PalettePicker({super.key, required this.selectedIndex, required this.onSelect}); + + @override + Widget build(BuildContext context) { + return Column( + children: List.generate(appPalettes.length, (i) { + final p = appPalettes[i]; + final isSelected = i == selectedIndex; + return GestureDetector( + onTap: () => onSelect(i), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isSelected ? p.primary : Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected ? p.primary : Colors.grey.shade300, + width: isSelected ? 3 : 1.5, + ), + boxShadow: isSelected + ? [BoxShadow(color: p.primary.withAlpha(80), blurRadius: 8, offset: const Offset(0, 3))] + : [], + ), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: p.primary, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + ), + ), + const SizedBox(width: 8), + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: p.surface, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1.5), + ), + ), + const SizedBox(width: 16), + Text( + p.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: isSelected ? Colors.white : Colors.black87, + ), + ), + const Spacer(), + if (isSelected) const Icon(Icons.check_circle, color: Colors.white, size: 22), + ], + ), + ), + ); + }), + ); + } +} diff --git a/lib/presentation/widgets/system/section_title.dart b/lib/presentation/widgets/system/section_title.dart new file mode 100644 index 0000000..b956f59 --- /dev/null +++ b/lib/presentation/widgets/system/section_title.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +/// Titre de section avec une barre verticale colorée à gauche. +class SectionTitle extends StatelessWidget { + final String text; + final Color color; + const SectionTitle({super.key, required this.text, required this.color}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container(width: 4, height: 20, color: color), + const SizedBox(width: 8), + Text( + text, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: color, letterSpacing: 2), + ), + ], + ); + } +} diff --git a/lib/presentation/widgets/system/system_header.dart b/lib/presentation/widgets/system/system_header.dart new file mode 100644 index 0000000..6adc2bd --- /dev/null +++ b/lib/presentation/widgets/system/system_header.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +/// En-tête de la page Système. +class SystemHeader extends StatelessWidget { + final Color primaryColor; + const SystemHeader({super.key, required this.primaryColor}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: primaryColor, + border: Border(bottom: BorderSide(color: primaryColor.withAlpha(180), width: 3)), + ), + child: const Text( + 'SYSTÈME', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold, letterSpacing: 4), + ), + ); + } +} diff --git a/lib/presentation/widgets/system/system_stats.dart b/lib/presentation/widgets/system/system_stats.dart new file mode 100644 index 0000000..3a1be24 --- /dev/null +++ b/lib/presentation/widgets/system/system_stats.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +/// Donnée d'une statistique affichée dans la grille. +class StatItem { + final String label; + final String value; + final IconData icon; + const StatItem({required this.label, required this.value, required this.icon}); +} + +/// Grille 2 colonnes de cartes de statistiques. +class StatsGrid extends StatelessWidget { + final Color primaryColor; + final List items; + const StatsGrid({super.key, required this.primaryColor, required this.items}); + + @override + Widget build(BuildContext context) { + return GridView.count( + crossAxisCount: 2, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 1.4, + children: items.map((item) => _StatCard(item: item, color: primaryColor)).toList(), + ); + } +} + +class _StatCard extends StatelessWidget { + final StatItem item; + final Color color; + const _StatCard({required this.item, required this.color}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: color.withAlpha(80), width: 2), + boxShadow: [ + BoxShadow(color: Colors.black.withAlpha(25), blurRadius: 4, offset: const Offset(0, 2)), + ], + ), + padding: const EdgeInsets.all(12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(item.icon, color: color, size: 28), + const SizedBox(height: 6), + Text(item.value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color)), + Text( + item.label, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 12, color: Colors.black54), + ), + ], + ), + ); + } +}