MAJ0.1 accueil et historique
This commit is contained in:
@@ -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:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
@@ -28,6 +21,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
List<Session> _sessions = [];
|
||||
bool _isLoading = true;
|
||||
TargetType? _filterType;
|
||||
DateTime? _selectedDate; // --- AJOUT : Variable pour le filtre date ---
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -37,16 +31,25 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
|
||||
Future<void> _loadSessions() async {
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final repository = context.read<SessionRepository>();
|
||||
final sessions = await repository.getAllSessions(
|
||||
targetType: _filterType,
|
||||
);
|
||||
// On récupère toutes les sessions. Le filtrage visuel se fera dans la liste
|
||||
// ou tu peux adapter ton repository pour accepter un DateTime.
|
||||
final sessions = await repository.getAllSessions(targetType: _filterType);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_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;
|
||||
});
|
||||
}
|
||||
@@ -55,7 +58,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur de chargement: $e'),
|
||||
content: Text('Erreur: $e'),
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -71,55 +89,91 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
actions: [
|
||||
PopupMenuButton<TargetType?>(
|
||||
icon: const Icon(Icons.filter_list),
|
||||
tooltip: 'Filtrer',
|
||||
onSelected: (type) {
|
||||
setState(() => _filterType = type);
|
||||
_loadSessions();
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
const PopupMenuItem(
|
||||
value: null,
|
||||
child: Text('Tous'),
|
||||
const PopupMenuItem(value: null, child: Text('Tous')),
|
||||
...TargetType.values.map(
|
||||
(type) =>
|
||||
PopupMenuItem(value: type, child: Text(type.displayName)),
|
||||
),
|
||||
...TargetType.values.map((type) => PopupMenuItem(
|
||||
value: type,
|
||||
child: Text(type.displayName),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _sessions.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildContent(),
|
||||
// --- MODIFICATION : Utilisation d'une Column pour fixer le bas ---
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
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() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppConstants.defaultPadding),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.history, size: 64, color: Colors.grey[400]),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.history, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Aucune session trouvée'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -129,49 +183,27 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
onRefresh: _loadSessions,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
// Chart section
|
||||
if (_sessions.length >= 2)
|
||||
if (_sessions.length >= 2 && _selectedDate == null)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppConstants.defaultPadding),
|
||||
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(
|
||||
padding: const EdgeInsets.all(AppConstants.defaultPadding),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final session = _sessions[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: SessionListItem(
|
||||
session: session,
|
||||
onTap: () => _openSessionDetail(session),
|
||||
onDelete: () => _deleteSession(session),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: _sessions.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final session = _sessions[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: SessionListItem(
|
||||
session: session,
|
||||
onTap: () => _openSessionDetail(session),
|
||||
onDelete: () => _deleteSession(session),
|
||||
),
|
||||
);
|
||||
}, 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 {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => SessionDetailScreen(session: session),
|
||||
),
|
||||
MaterialPageRoute(builder: (_) => SessionDetailScreen(session: session)),
|
||||
);
|
||||
_loadSessions(); // Refresh in case session was deleted
|
||||
_loadSessions();
|
||||
}
|
||||
|
||||
Future<void> _deleteSession(Session session) async {
|
||||
@@ -204,7 +235,10 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
),
|
||||
TextButton(
|
||||
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>();
|
||||
await repository.deleteSession(session.id);
|
||||
_loadSessions();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Session supprimee')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: $e'),
|
||||
backgroundColor: AppTheme.errorColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
// ... snackbar d'erreur
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:provider/provider.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
@@ -46,6 +40,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
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'),
|
||||
actions: [
|
||||
IconButton(
|
||||
@@ -94,7 +95,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor.withValues(alpha: 0.1),
|
||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -106,16 +107,16 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Analyse de Cibles',
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Scannez vos cibles et analysez vos performances',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge?.copyWith(color: AppTheme.textSecondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
@@ -147,14 +148,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
children: [
|
||||
Text(
|
||||
'Statistiques',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Première ligne de vignettes (Sessions et Tirs)
|
||||
Row(
|
||||
children: [
|
||||
// --- BOUTON SESSIONS (Redirige vers Statistiques) ---
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _navigateToStatistics(context),
|
||||
@@ -168,7 +170,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Ce bouton reste statique (ou tu peux ajouter une action)
|
||||
Expanded(
|
||||
child: StatsCard(
|
||||
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(
|
||||
children: [
|
||||
// --- BOUTON SCORE MOYEN (Redirige vers Historique) ---
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _navigateToHistory(context),
|
||||
@@ -196,7 +219,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Ce bouton reste statique
|
||||
Expanded(
|
||||
child: StatsCard(
|
||||
icon: Icons.emoji_events,
|
||||
@@ -211,12 +233,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// --- MÉTHODES DE NAVIGATION ---
|
||||
|
||||
void _navigateToCapture(BuildContext context) async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const CaptureScreen()),
|
||||
);
|
||||
// Refresh stats when returning
|
||||
_loadStats();
|
||||
}
|
||||
|
||||
@@ -225,7 +248,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const HistoryScreen()),
|
||||
);
|
||||
// Refresh stats when returning
|
||||
_loadStats();
|
||||
}
|
||||
|
||||
@@ -234,7 +256,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const StatisticsScreen()),
|
||||
);
|
||||
// Refresh stats when returning
|
||||
_loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user