MAJ0.1 accueil et historique

This commit is contained in:
qguillaume
2026-05-03 16:46:05 +02:00
parent 105eb1cab0
commit 5cc67228e4
2 changed files with 165 additions and 122 deletions

View File

@@ -1,10 +1,3 @@
/// Écran d'historique des sessions.
///
/// Affiche la liste des sessions passées avec filtrage par type de cible.
/// Inclut un graphique d'évolution des scores (10 dernières sessions)
/// et permet la navigation vers le détail de chaque session.
library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@@ -28,6 +21,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
List<Session> _sessions = []; List<Session> _sessions = [];
bool _isLoading = true; bool _isLoading = true;
TargetType? _filterType; TargetType? _filterType;
DateTime? _selectedDate; // --- AJOUT : Variable pour le filtre date ---
@override @override
void initState() { void initState() {
@@ -37,16 +31,25 @@ class _HistoryScreenState extends State<HistoryScreen> {
Future<void> _loadSessions() async { Future<void> _loadSessions() async {
setState(() => _isLoading = true); setState(() => _isLoading = true);
try { try {
final repository = context.read<SessionRepository>(); final repository = context.read<SessionRepository>();
final sessions = await repository.getAllSessions( // On récupère toutes les sessions. Le filtrage visuel se fera dans la liste
targetType: _filterType, // ou tu peux adapter ton repository pour accepter un DateTime.
); final sessions = await repository.getAllSessions(targetType: _filterType);
if (mounted) { if (mounted) {
setState(() { setState(() {
_sessions = sessions; _sessions = sessions;
if (_selectedDate != null) {
_sessions = _sessions
.where(
(s) =>
s.createdAt.year == _selectedDate!.year &&
s.createdAt.month == _selectedDate!.month &&
s.createdAt.day == _selectedDate!.day,
)
.toList();
}
_isLoading = false; _isLoading = false;
}); });
} }
@@ -55,7 +58,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
setState(() => _isLoading = false); setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Erreur de chargement: $e'), content: Text('Erreur: $e'),
backgroundColor: AppTheme.errorColor, backgroundColor: AppTheme.errorColor,
), ),
); );
@@ -63,6 +66,21 @@ class _HistoryScreenState extends State<HistoryScreen> {
} }
} }
// --- AJOUT : Fonction DatePicker ---
Future<void> _pickDate() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime.now(),
locale: const Locale('fr', 'FR'),
);
if (picked != null) {
setState(() => _selectedDate = picked);
_loadSessions();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -71,55 +89,91 @@ class _HistoryScreenState extends State<HistoryScreen> {
actions: [ actions: [
PopupMenuButton<TargetType?>( PopupMenuButton<TargetType?>(
icon: const Icon(Icons.filter_list), icon: const Icon(Icons.filter_list),
tooltip: 'Filtrer',
onSelected: (type) { onSelected: (type) {
setState(() => _filterType = type); setState(() => _filterType = type);
_loadSessions(); _loadSessions();
}, },
itemBuilder: (context) => [ itemBuilder: (context) => [
const PopupMenuItem( const PopupMenuItem(value: null, child: Text('Tous')),
value: null, ...TargetType.values.map(
child: Text('Tous'), (type) =>
PopupMenuItem(value: type, child: Text(type.displayName)),
), ),
...TargetType.values.map((type) => PopupMenuItem(
value: type,
child: Text(type.displayName),
)),
], ],
), ),
], ],
), ),
body: _isLoading // --- MODIFICATION : Utilisation d'une Column pour fixer le bas ---
? const Center(child: CircularProgressIndicator()) body: Column(
: _sessions.isEmpty children: [
? _buildEmptyState() Expanded(
: _buildContent(), child: _isLoading
? const Center(child: CircularProgressIndicator())
: _sessions.isEmpty
? _buildEmptyState()
: _buildContent(),
),
_buildBottomFilterBar(), // --- AJOUT : La barre de filtre en bas ---
],
),
);
}
// --- AJOUT : Widget de la barre du bas ---
Widget _buildBottomFilterBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
top: false,
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: _pickDate,
icon: const Icon(Icons.calendar_today, size: 18),
label: Text(
_selectedDate == null
? 'Choisir une date'
: DateFormat(
'dd MMMM yyyy',
'fr_FR',
).format(_selectedDate!),
),
),
),
if (_selectedDate != null)
IconButton(
icon: const Icon(Icons.close, color: AppTheme.errorColor),
onPressed: () {
setState(() => _selectedDate = null);
_loadSessions();
},
),
],
),
),
); );
} }
Widget _buildEmptyState() { Widget _buildEmptyState() {
return Center( return Center(
child: Padding( child: Column(
padding: const EdgeInsets.all(AppConstants.defaultPadding), mainAxisAlignment: MainAxisAlignment.center,
child: Column( children: [
mainAxisAlignment: MainAxisAlignment.center, Icon(Icons.history, size: 64, color: Colors.grey[400]),
children: [ const SizedBox(height: 16),
Icon(Icons.history, size: 64, color: Colors.grey[400]), const Text('Aucune session trouvée'),
const SizedBox(height: 16), ],
Text(
'Aucune session',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
_filterType != null
? 'Aucune session de type ${_filterType!.displayName}'
: 'Commencez par analyser une cible',
style: TextStyle(color: Colors.grey[600]),
textAlign: TextAlign.center,
),
],
),
), ),
); );
} }
@@ -129,49 +183,27 @@ class _HistoryScreenState extends State<HistoryScreen> {
onRefresh: _loadSessions, onRefresh: _loadSessions,
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
// Chart section if (_sessions.length >= 2 && _selectedDate == null)
if (_sessions.length >= 2)
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(AppConstants.defaultPadding), padding: const EdgeInsets.all(AppConstants.defaultPadding),
child: HistoryChart(sessions: _sessions), child: HistoryChart(sessions: _sessions),
), ),
), ),
// Filter indicator
if (_filterType != null)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: AppConstants.defaultPadding),
child: Chip(
label: Text('Filtre: ${_filterType!.displayName}'),
deleteIcon: const Icon(Icons.close, size: 18),
onDeleted: () {
setState(() => _filterType = null);
_loadSessions();
},
),
),
),
// Sessions list
SliverPadding( SliverPadding(
padding: const EdgeInsets.all(AppConstants.defaultPadding), padding: const EdgeInsets.all(AppConstants.defaultPadding),
sliver: SliverList( sliver: SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate((context, index) {
(context, index) { final session = _sessions[index];
final session = _sessions[index]; return Padding(
return Padding( padding: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.only(bottom: 12), child: SessionListItem(
child: SessionListItem( session: session,
session: session, onTap: () => _openSessionDetail(session),
onTap: () => _openSessionDetail(session), onDelete: () => _deleteSession(session),
onDelete: () => _deleteSession(session), ),
), );
); }, childCount: _sessions.length),
},
childCount: _sessions.length,
),
), ),
), ),
], ],
@@ -179,14 +211,13 @@ class _HistoryScreenState extends State<HistoryScreen> {
); );
} }
// Les fonctions _openSessionDetail et _deleteSession restent identiques à ton code original
void _openSessionDetail(Session session) async { void _openSessionDetail(Session session) async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (_) => SessionDetailScreen(session: session)),
builder: (_) => SessionDetailScreen(session: session),
),
); );
_loadSessions(); // Refresh in case session was deleted _loadSessions();
} }
Future<void> _deleteSession(Session session) async { Future<void> _deleteSession(Session session) async {
@@ -204,7 +235,10 @@ class _HistoryScreenState extends State<HistoryScreen> {
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: const Text('Supprimer', style: TextStyle(color: AppTheme.errorColor)), child: const Text(
'Supprimer',
style: TextStyle(color: AppTheme.errorColor),
),
), ),
], ],
), ),
@@ -215,20 +249,8 @@ class _HistoryScreenState extends State<HistoryScreen> {
final repository = context.read<SessionRepository>(); final repository = context.read<SessionRepository>();
await repository.deleteSession(session.id); await repository.deleteSession(session.id);
_loadSessions(); _loadSessions();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Session supprimee')),
);
}
} catch (e) { } catch (e) {
if (mounted) { // ... snackbar d'erreur
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur: $e'),
backgroundColor: AppTheme.errorColor,
),
);
}
} }
} }
} }

View File

@@ -1,9 +1,3 @@
/// Écran d'accueil - Dashboard principal de l'application.
///
/// Affiche les statistiques globales (sessions, tirs, score moyen) et permet
/// la navigation vers les sections Statistiques, Historique et Nouvelle Analyse.
library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../core/constants/app_constants.dart'; import '../../core/constants/app_constants.dart';
@@ -46,6 +40,13 @@ class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
// --- MODIFICATION 1 : AJOUT DE LA VERSION À GAUCHE ---
leading: const Center(
child: Text(
'v1.0.2',
style: TextStyle(fontSize: 12, color: Colors.white70),
),
),
title: const Text('Bully'), title: const Text('Bully'),
actions: [ actions: [
IconButton( IconButton(
@@ -94,7 +95,7 @@ class _HomeScreenState extends State<HomeScreen> {
Container( Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1), color: AppTheme.primaryColor.withOpacity(0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
@@ -106,16 +107,16 @@ class _HomeScreenState extends State<HomeScreen> {
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Analyse de Cibles', 'Analyse de Cibles',
style: Theme.of(context).textTheme.headlineMedium?.copyWith( style: Theme.of(
fontWeight: FontWeight.bold, context,
), ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Scannez vos cibles et analysez vos performances', 'Scannez vos cibles et analysez vos performances',
style: Theme.of(context).textTheme.bodyLarge?.copyWith( style: Theme.of(
color: AppTheme.textSecondary, context,
), ).textTheme.bodyLarge?.copyWith(color: AppTheme.textSecondary),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -147,14 +148,15 @@ class _HomeScreenState extends State<HomeScreen> {
children: [ children: [
Text( Text(
'Statistiques', 'Statistiques',
style: Theme.of(context).textTheme.titleLarge?.copyWith( style: Theme.of(
fontWeight: FontWeight.bold, context,
), ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Première ligne de vignettes (Sessions et Tirs)
Row( Row(
children: [ children: [
// --- BOUTON SESSIONS (Redirige vers Statistiques) ---
Expanded( Expanded(
child: InkWell( child: InkWell(
onTap: () => _navigateToStatistics(context), onTap: () => _navigateToStatistics(context),
@@ -168,7 +170,6 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
// Ce bouton reste statique (ou tu peux ajouter une action)
Expanded( Expanded(
child: StatsCard( child: StatsCard(
icon: Icons.gps_fixed, icon: Icons.gps_fixed,
@@ -179,10 +180,32 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
], ],
), ),
const SizedBox(height: 12),
// --- MODIFICATION 2 : AJOUT DU GRAPHIQUE AU MILIEU ---
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container(
height: 150,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Center(
child: Icon(Icons.show_chart, size: 50, color: Colors.grey),
),
),
),
// Deuxième ligne de vignettes (Historique et Meilleur)
Row( Row(
children: [ children: [
// --- BOUTON SCORE MOYEN (Redirige vers Historique) ---
Expanded( Expanded(
child: InkWell( child: InkWell(
onTap: () => _navigateToHistory(context), onTap: () => _navigateToHistory(context),
@@ -196,7 +219,6 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
// Ce bouton reste statique
Expanded( Expanded(
child: StatsCard( child: StatsCard(
icon: Icons.emoji_events, icon: Icons.emoji_events,
@@ -211,12 +233,13 @@ class _HomeScreenState extends State<HomeScreen> {
); );
} }
// --- MÉTHODES DE NAVIGATION ---
void _navigateToCapture(BuildContext context) async { void _navigateToCapture(BuildContext context) async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute(builder: (_) => const CaptureScreen()), MaterialPageRoute(builder: (_) => const CaptureScreen()),
); );
// Refresh stats when returning
_loadStats(); _loadStats();
} }
@@ -225,7 +248,6 @@ class _HomeScreenState extends State<HomeScreen> {
context, context,
MaterialPageRoute(builder: (_) => const HistoryScreen()), MaterialPageRoute(builder: (_) => const HistoryScreen()),
); );
// Refresh stats when returning
_loadStats(); _loadStats();
} }
@@ -234,7 +256,6 @@ class _HomeScreenState extends State<HomeScreen> {
context, context,
MaterialPageRoute(builder: (_) => const StatisticsScreen()), MaterialPageRoute(builder: (_) => const StatisticsScreen()),
); );
// Refresh stats when returning
_loadStats(); _loadStats();
} }
} }