feat: Implement centralized error handling with ErrorService; replace print statements with logging in services and blocs
feat: Add ErrorContent widget for displaying error messages in dialogs and bottom sheets refactor: Update GroupBloc and GroupRepository to utilize ErrorService for error logging refactor: Enhance user and trip services to log errors using ErrorService refactor: Clean up debug print statements in GroupContent and related components
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import 'group_event.dart';
|
import 'group_event.dart';
|
||||||
import 'group_state.dart';
|
import 'group_state.dart';
|
||||||
import '../../repositories/group_repository.dart';
|
import '../../repositories/group_repository.dart';
|
||||||
@@ -8,6 +9,7 @@ import '../../data/models/group.dart';
|
|||||||
class GroupBloc extends Bloc<GroupEvent, GroupState> {
|
class GroupBloc extends Bloc<GroupEvent, GroupState> {
|
||||||
final GroupRepository _repository;
|
final GroupRepository _repository;
|
||||||
StreamSubscription? _groupsSubscription;
|
StreamSubscription? _groupsSubscription;
|
||||||
|
final _errorService = ErrorService();
|
||||||
|
|
||||||
GroupBloc(this._repository) : super(GroupInitial()) {
|
GroupBloc(this._repository) : super(GroupInitial()) {
|
||||||
on<LoadGroupsByUserId>(_onLoadGroupsByUserId);
|
on<LoadGroupsByUserId>(_onLoadGroupsByUserId);
|
||||||
@@ -25,32 +27,19 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
|
|||||||
LoadGroupsByUserId event,
|
LoadGroupsByUserId event,
|
||||||
Emitter<GroupState> emit,
|
Emitter<GroupState> emit,
|
||||||
) async {
|
) async {
|
||||||
print('===== GroupBloc: _onLoadGroupsByUserId START =====');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
emit(GroupLoading());
|
emit(GroupLoading());
|
||||||
print('>>> GroupBloc: État GroupLoading émis');
|
|
||||||
|
|
||||||
await _groupsSubscription?.cancel();
|
await _groupsSubscription?.cancel();
|
||||||
print('>>> GroupBloc: Ancien subscription annulé');
|
|
||||||
|
|
||||||
_groupsSubscription = _repository.getGroupsByUserId(event.userId).listen(
|
_groupsSubscription = _repository.getGroupsByUserId(event.userId).listen(
|
||||||
(groups) {
|
(groups) {
|
||||||
print('===== GroupBloc: Stream reçu ${groups.length} groupes =====');
|
|
||||||
// Utiliser un événement interne au lieu d'émettre directement
|
|
||||||
add(_GroupsUpdated(groups));
|
add(_GroupsUpdated(groups));
|
||||||
},
|
},
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
print('===== GroupBloc: Erreur stream: $error =====');
|
|
||||||
add(_GroupsUpdated([], error: error.toString()));
|
add(_GroupsUpdated([], error: error.toString()));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
print('>>> GroupBloc: Subscription créé avec succès');
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('===== GroupBloc: Exception _onLoadGroupsByUserId =====');
|
_errorService.logError(e.toString(), stackTrace);
|
||||||
print('Exception: $e');
|
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
emit(GroupError(e.toString()));
|
emit(GroupError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,16 +49,11 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
|
|||||||
_GroupsUpdated event,
|
_GroupsUpdated event,
|
||||||
Emitter<GroupState> emit,
|
Emitter<GroupState> emit,
|
||||||
) async {
|
) async {
|
||||||
print('===== GroupBloc: _onGroupsUpdated =====');
|
|
||||||
print('Groupes reçus: ${event.groups.length}');
|
|
||||||
|
|
||||||
if (event.error != null) {
|
if (event.error != null) {
|
||||||
print('>>> Émission GroupError: ${event.error}');
|
_errorService.logError(event.error!, StackTrace.current);
|
||||||
emit(GroupError(event.error!));
|
emit(GroupError(event.error!));
|
||||||
} else {
|
} else {
|
||||||
print('>>> Émission GroupsLoaded avec ${event.groups.length} groupes');
|
|
||||||
emit(GroupsLoaded(event.groups));
|
emit(GroupsLoaded(event.groups));
|
||||||
print('>>> GroupsLoaded émis avec succès !');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +156,6 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
print('===== GroupBloc: close() =====');
|
|
||||||
_groupsSubscription?.cancel();
|
_groupsSubscription?.cancel();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|||||||
237
lib/components/error/error_content.dart
Normal file
237
lib/components/error/error_content.dart
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ErrorContent extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String message;
|
||||||
|
final VoidCallback? onRetry;
|
||||||
|
final VoidCallback? onClose;
|
||||||
|
final IconData icon;
|
||||||
|
final Color? iconColor;
|
||||||
|
|
||||||
|
const ErrorContent({
|
||||||
|
super.key,
|
||||||
|
this.title = 'Une erreur est survenue',
|
||||||
|
required this.message,
|
||||||
|
this.onRetry,
|
||||||
|
this.onClose,
|
||||||
|
this.icon = Icons.error_outline,
|
||||||
|
this.iconColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final defaultIconColor = iconColor ?? Colors.red[400];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDarkMode ? Colors.grey[900] : Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
|
blurRadius: 10,
|
||||||
|
spreadRadius: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Bouton fermer en haut à droite
|
||||||
|
if (onClose != null)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: onClose,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Icône d'erreur
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: defaultIconColor?.withValues(alpha: 0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 48,
|
||||||
|
color: defaultIconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Titre
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isDarkMode ? Colors.white : Colors.black87,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Message d'erreur
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Boutons d'action
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (onRetry != null) ...[
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: onRetry,
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: const Text('Réessayer'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (onClose != null) const SizedBox(width: 12),
|
||||||
|
],
|
||||||
|
if (onClose != null)
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: onClose,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
label: const Text('Fermer'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction helper pour afficher l'erreur en dialog
|
||||||
|
void showErrorDialog(
|
||||||
|
BuildContext context, {
|
||||||
|
String title = 'Une erreur est survenue',
|
||||||
|
required String message,
|
||||||
|
VoidCallback? onRetry,
|
||||||
|
IconData icon = Icons.error_outline,
|
||||||
|
Color? iconColor,
|
||||||
|
bool barrierDismissible = true,
|
||||||
|
}) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: barrierDismissible,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: ErrorContent(
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
icon: icon,
|
||||||
|
iconColor: iconColor,
|
||||||
|
onRetry: onRetry != null
|
||||||
|
? () {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
onRetry();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
onClose: () => Navigator.of(dialogContext).pop(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction helper pour afficher l'erreur en bottom sheet
|
||||||
|
void showErrorBottomSheet(
|
||||||
|
BuildContext context, {
|
||||||
|
String title = 'Une erreur est survenue',
|
||||||
|
required String message,
|
||||||
|
VoidCallback? onRetry,
|
||||||
|
IconData icon = Icons.error_outline,
|
||||||
|
Color? iconColor,
|
||||||
|
bool isDismissible = true,
|
||||||
|
}) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isDismissible: isDismissible,
|
||||||
|
enableDrag: isDismissible,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
builder: (BuildContext sheetContext) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: ErrorContent(
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
icon: icon,
|
||||||
|
iconColor: iconColor,
|
||||||
|
onRetry: onRetry != null
|
||||||
|
? () {
|
||||||
|
Navigator.of(sheetContext).pop();
|
||||||
|
onRetry();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
onClose: () => Navigator.of(sheetContext).pop(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction helper pour afficher en SnackBar (pour erreurs mineures)
|
||||||
|
void showErrorSnackBar(
|
||||||
|
BuildContext context, {
|
||||||
|
required String message,
|
||||||
|
VoidCallback? onRetry,
|
||||||
|
Duration duration = const Duration(seconds: 4),
|
||||||
|
}) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
duration: duration,
|
||||||
|
action: onRetry != null
|
||||||
|
? SnackBarAction(
|
||||||
|
label: 'Réessayer',
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: onRetry,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:travel_mate/components/error/error_content.dart';
|
||||||
import '../../blocs/user/user_bloc.dart';
|
import '../../blocs/user/user_bloc.dart';
|
||||||
import '../../blocs/user/user_state.dart' as user_state;
|
import '../../blocs/user/user_state.dart' as user_state;
|
||||||
import '../../blocs/group/group_bloc.dart';
|
import '../../blocs/group/group_bloc.dart';
|
||||||
@@ -18,55 +19,39 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
print('===== GroupContent: initState =====');
|
|
||||||
|
|
||||||
// Charger immédiatement sans attendre le prochain frame
|
// Charger immédiatement sans attendre le prochain frame
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
print('===== PostFrameCallback déclenché =====');
|
|
||||||
_loadInitialData();
|
_loadInitialData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadInitialData() {
|
void _loadInitialData() {
|
||||||
print('===== _loadInitialData START =====');
|
|
||||||
try {
|
try {
|
||||||
final userState = context.read<UserBloc>().state;
|
final userState = context.read<UserBloc>().state;
|
||||||
print('UserBloc state type: ${userState.runtimeType}');
|
|
||||||
print('UserBloc state: $userState');
|
|
||||||
|
|
||||||
if (userState is user_state.UserLoaded) {
|
if (userState is user_state.UserLoaded) {
|
||||||
final userId = userState.user.id;
|
final userId = userState.user.id;
|
||||||
print('✓ User chargé, ID: $userId');
|
|
||||||
print('>>> Envoi de LoadGroupsByUserId <<<');
|
|
||||||
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
||||||
print('>>> LoadGroupsByUserId envoyé <<<');
|
|
||||||
} else {
|
} else {
|
||||||
print('✗ UserState n\'est pas UserLoaded');
|
throw Exception('Utilisateur non connecté');
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
print('===== ERREUR _loadInitialData =====');
|
_buildErrorState(e.toString(), '', true);
|
||||||
print('Exception: $e');
|
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
print('===== GroupContent: build =====');
|
|
||||||
|
|
||||||
return BlocBuilder<UserBloc, user_state.UserState>(
|
return BlocBuilder<UserBloc, user_state.UserState>(
|
||||||
builder: (context, userState) {
|
builder: (context, userState) {
|
||||||
print('>>> UserBloc builder - state type: ${userState.runtimeType}');
|
|
||||||
|
|
||||||
if (userState is user_state.UserLoading) {
|
if (userState is user_state.UserLoading) {
|
||||||
print('État: UserLoading');
|
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
body: Center(child: CircularProgressIndicator()),
|
body: Center(child: CircularProgressIndicator()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userState is user_state.UserError) {
|
if (userState is user_state.UserError) {
|
||||||
print('État: UserError - ${userState.message}');
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -82,23 +67,15 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userState is! user_state.UserLoaded) {
|
if (userState is! user_state.UserLoaded) {
|
||||||
print('État: Utilisateur non connecté');
|
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
body: Center(child: Text('Utilisateur non connecté')),
|
body: Center(child: Text('Utilisateur non connecté')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
print('✓ État: UserLoaded');
|
|
||||||
final user = userState.user;
|
final user = userState.user;
|
||||||
|
|
||||||
return BlocConsumer<GroupBloc, GroupState>(
|
return BlocConsumer<GroupBloc, GroupState>(
|
||||||
listener: (context, groupState) {
|
listener: (context, groupState) {
|
||||||
print('===== GroupBloc LISTENER =====');
|
|
||||||
print('State type: ${groupState.runtimeType}');
|
|
||||||
print('State: $groupState');
|
|
||||||
|
|
||||||
if (groupState is GroupOperationSuccess) {
|
if (groupState is GroupOperationSuccess) {
|
||||||
print('>>> GroupOperationSuccess: ${groupState.message}');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(groupState.message),
|
content: Text(groupState.message),
|
||||||
@@ -106,7 +83,6 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (groupState is GroupError) {
|
} else if (groupState is GroupError) {
|
||||||
print('>>> GroupError: ${groupState.message}');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(groupState.message),
|
content: Text(groupState.message),
|
||||||
@@ -116,19 +92,6 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, groupState) {
|
builder: (context, groupState) {
|
||||||
print('===== GroupBloc BUILDER =====');
|
|
||||||
print('State type: ${groupState.runtimeType}');
|
|
||||||
print('State: $groupState');
|
|
||||||
|
|
||||||
// TEST: Afficher le type exact
|
|
||||||
if (groupState is GroupsLoaded) {
|
|
||||||
print('✓✓✓ GroupsLoaded détecté ! ✓✓✓');
|
|
||||||
print('Nombre de groupes: ${groupState.groups.length}');
|
|
||||||
for (var i = 0; i < groupState.groups.length; i++) {
|
|
||||||
print(' Groupe $i: ${groupState.groups[i].name}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: _buildContent(groupState, user.id),
|
child: _buildContent(groupState, user.id),
|
||||||
@@ -141,11 +104,7 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(GroupState groupState, String userId) {
|
Widget _buildContent(GroupState groupState, String userId) {
|
||||||
print('===== _buildContent =====');
|
|
||||||
print('State type: ${groupState.runtimeType}');
|
|
||||||
|
|
||||||
if (groupState is GroupLoading) {
|
if (groupState is GroupLoading) {
|
||||||
print('>>> Affichage: Loading');
|
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -159,24 +118,18 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (groupState is GroupError) {
|
if (groupState is GroupError) {
|
||||||
print('>>> Affichage: Error');
|
return _buildErrorState(groupState.message, userId, true);
|
||||||
return _buildErrorState(groupState.message, userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupState is GroupsLoaded) {
|
if (groupState is GroupsLoaded) {
|
||||||
print('>>> Affichage: GroupsLoaded');
|
|
||||||
print('Groupes: ${groupState.groups.length}');
|
|
||||||
|
|
||||||
if (groupState.groups.isEmpty) {
|
if (groupState.groups.isEmpty) {
|
||||||
print('>>> Affichage: Empty');
|
|
||||||
return _buildEmptyState();
|
return _buildEmptyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
print('>>> Affichage: Liste des groupes');
|
|
||||||
return _buildGroupsList(groupState.groups, userId);
|
return _buildGroupsList(groupState.groups, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
print('>>> Affichage: Initial/Unknown');
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -185,7 +138,6 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print('>>> Bouton refresh cliqué');
|
|
||||||
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
||||||
},
|
},
|
||||||
child: const Text('Charger les groupes'),
|
child: const Text('Charger les groupes'),
|
||||||
@@ -196,12 +148,8 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGroupsList(List<Group> groups, String userId) {
|
Widget _buildGroupsList(List<Group> groups, String userId) {
|
||||||
print('===== _buildGroupsList =====');
|
|
||||||
print('Nombre de groupes à afficher: ${groups.length}');
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
print('>>> Pull to refresh');
|
|
||||||
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
},
|
},
|
||||||
@@ -221,20 +169,17 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
|
|
||||||
// IMPORTANT: Utiliser un simple Column au lieu de GridView pour tester
|
// IMPORTANT: Utiliser un simple Column au lieu de GridView pour tester
|
||||||
...groups.map((group) {
|
...groups.map((group) {
|
||||||
print('Création widget pour: ${group.name}');
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: _buildSimpleGroupCard(group),
|
child: _buildSimpleGroupCard(group),
|
||||||
);
|
);
|
||||||
}).toList(),
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSimpleGroupCard(Group group) {
|
Widget _buildSimpleGroupCard(Group group) {
|
||||||
print('===== _buildSimpleGroupCard: ${group.name} =====');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange];
|
final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange];
|
||||||
final color = colors[group.name.hashCode.abs() % colors.length];
|
final color = colors[group.name.hashCode.abs() % colors.length];
|
||||||
@@ -249,8 +194,6 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
memberInfo += '\n$names';
|
memberInfo += '\n$names';
|
||||||
}
|
}
|
||||||
|
|
||||||
print('Card créée avec succès');
|
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@@ -265,14 +208,11 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
subtitle: Text(memberInfo),
|
subtitle: Text(memberInfo),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
print('Tap sur: ${group.name}');
|
|
||||||
_openGroupChat(group);
|
_openGroupChat(group);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
print('ERREUR dans _buildSimpleGroupCard: $e');
|
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
return Card(
|
return Card(
|
||||||
color: Colors.red[100],
|
color: Colors.red[100],
|
||||||
child: const ListTile(
|
child: const ListTile(
|
||||||
@@ -284,7 +224,6 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() {
|
Widget _buildEmptyState() {
|
||||||
print('===== _buildEmptyState =====');
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(32),
|
padding: const EdgeInsets.all(32),
|
||||||
@@ -306,39 +245,49 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorState(String error, String userId) {
|
Widget _buildErrorState(String error, String userId, bool retry) {
|
||||||
print('===== _buildErrorState =====');
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
return Center(
|
if (mounted) {
|
||||||
child: Padding(
|
if (retry) {
|
||||||
padding: const EdgeInsets.all(32),
|
if (userId == '') {
|
||||||
child: Column(
|
showErrorDialog(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
context,
|
||||||
children: [
|
title: 'Erreur utilisateur',
|
||||||
const Icon(Icons.error, size: 64, color: Colors.red),
|
message: 'Utilisateur non connecté. Veuillez vous reconnecter.',
|
||||||
const SizedBox(height: 16),
|
icon: Icons.error,
|
||||||
const Text('Erreur', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
iconColor: Colors.red,
|
||||||
const SizedBox(height: 8),
|
onRetry: () {
|
||||||
Text(error, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12)),
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
const SizedBox(height: 16),
|
},
|
||||||
ElevatedButton.icon(
|
);
|
||||||
onPressed: () {
|
} else {
|
||||||
print('>>> Bouton réessayer cliqué');
|
showErrorDialog(
|
||||||
|
context,
|
||||||
|
title: 'Erreur de chargement',
|
||||||
|
message: error,
|
||||||
|
icon: Icons.cloud_off,
|
||||||
|
iconColor: Colors.orange,
|
||||||
|
onRetry: () {
|
||||||
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
context.read<GroupBloc>().add(LoadGroupsByUserId(userId));
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh),
|
);
|
||||||
label: const Text('Réessayer'),
|
}
|
||||||
),
|
} else {
|
||||||
],
|
showErrorDialog(
|
||||||
),
|
context,
|
||||||
),
|
title: 'Erreur',
|
||||||
);
|
message: error,
|
||||||
|
icon: Icons.error,
|
||||||
|
iconColor: Colors.red,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openGroupChat(Group group) {
|
void _openGroupChat(Group group) {
|
||||||
print('===== _openGroupChat: ${group.name} =====');
|
|
||||||
print('Group ID: ${group.id}');
|
|
||||||
print('Group members: ${group.members.length}');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Afficher juste un message, pas de navigation pour l'instant
|
// Afficher juste un message, pas de navigation pour l'instant
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -359,7 +308,7 @@ class _GroupContentState extends State<GroupContent> {
|
|||||||
// );
|
// );
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ERREUR openGroupChat: $e');
|
_buildErrorState(e.toString(), '', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:travel_mate/data/models/trip.dart';
|
import 'package:travel_mate/data/models/trip.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import '../../blocs/user/user_bloc.dart';
|
import '../../blocs/user/user_bloc.dart';
|
||||||
import '../../blocs/user/user_state.dart' as user_state;
|
import '../../blocs/user/user_state.dart' as user_state;
|
||||||
import '../../blocs/trip/trip_bloc.dart';
|
import '../../blocs/trip/trip_bloc.dart';
|
||||||
@@ -19,6 +20,7 @@ class CreateTripContent extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CreateTripContentState extends State<CreateTripContent> {
|
class _CreateTripContentState extends State<CreateTripContent> {
|
||||||
|
final _errorService = ErrorService();
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _titleController = TextEditingController();
|
final _titleController = TextEditingController();
|
||||||
final _descriptionController = TextEditingController();
|
final _descriptionController = TextEditingController();
|
||||||
@@ -528,7 +530,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération de l\'utilisateur: $e');
|
_errorService.logError('Erreur lors de la récupération de l\'utilisateur $email: $e', StackTrace.current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ class Trip {
|
|||||||
// Essayer de parser comme ISO 8601
|
// Essayer de parser comme ISO 8601
|
||||||
return DateTime.parse(dateValue);
|
return DateTime.parse(dateValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur parsing date string: $dateValue - $e');
|
|
||||||
return DateTime.now();
|
return DateTime.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,12 +71,9 @@ class Trip {
|
|||||||
// Traiter comme millisecondes
|
// Traiter comme millisecondes
|
||||||
return DateTime.fromMillisecondsSinceEpoch(dateValue);
|
return DateTime.fromMillisecondsSinceEpoch(dateValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur parsing date int: $dateValue - $e');
|
|
||||||
return DateTime.now();
|
return DateTime.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print('Type de date non supporté: ${dateValue.runtimeType} - $dateValue');
|
|
||||||
return DateTime.now();
|
return DateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import 'blocs/auth/auth_bloc.dart';
|
import 'blocs/auth/auth_bloc.dart';
|
||||||
import 'blocs/auth/auth_event.dart';
|
import 'blocs/auth/auth_event.dart';
|
||||||
import 'blocs/auth/auth_state.dart';
|
import 'blocs/auth/auth_state.dart';
|
||||||
@@ -69,6 +70,7 @@ class MyApp extends StatelessWidget {
|
|||||||
builder: (context, themeState) {
|
builder: (context, themeState) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Travel Mate',
|
title: 'Travel Mate',
|
||||||
|
navigatorKey: ErrorService.navigatorKey,
|
||||||
themeMode: themeState.themeMode,
|
themeMode: themeState.themeMode,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import '../data/models/group.dart';
|
import '../data/models/group.dart';
|
||||||
import '../data/models/group_member.dart';
|
import '../data/models/group_member.dart';
|
||||||
|
|
||||||
class GroupRepository {
|
class GroupRepository {
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
final _errorService = ErrorService();
|
||||||
|
|
||||||
CollectionReference get _groupsCollection => _firestore.collection('groups');
|
CollectionReference get _groupsCollection => _firestore.collection('groups');
|
||||||
|
|
||||||
@@ -35,21 +37,15 @@ class GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Group>> getGroupsByUserId(String userId) {
|
Stream<List<Group>> getGroupsByUserId(String userId) {
|
||||||
print('===== GroupRepository: getGroupsByUserId START =====');
|
|
||||||
print('UserId recherché: $userId');
|
|
||||||
|
|
||||||
return _groupsCollection
|
return _groupsCollection
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.asyncMap((snapshot) async {
|
.asyncMap((snapshot) async {
|
||||||
print('===== GroupRepository: Nouveau snapshot (${DateTime.now()}) =====');
|
|
||||||
print('Nombre de documents: ${snapshot.docs.length}');
|
|
||||||
|
|
||||||
List<Group> userGroups = [];
|
List<Group> userGroups = [];
|
||||||
|
|
||||||
for (var groupDoc in snapshot.docs) {
|
for (var groupDoc in snapshot.docs) {
|
||||||
try {
|
try {
|
||||||
final groupId = groupDoc.id;
|
final groupId = groupDoc.id;
|
||||||
print('--- Vérification groupe: $groupId ---');
|
|
||||||
|
|
||||||
// Vérifier si l'utilisateur est membre
|
// Vérifier si l'utilisateur est membre
|
||||||
final memberDoc = await groupDoc.reference
|
final memberDoc = await groupDoc.reference
|
||||||
@@ -57,34 +53,24 @@ class GroupRepository {
|
|||||||
.doc(userId)
|
.doc(userId)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
print('Membre existe dans $groupId: ${memberDoc.exists}');
|
|
||||||
|
|
||||||
if (memberDoc.exists) {
|
if (memberDoc.exists) {
|
||||||
print('✓ Utilisateur trouvé dans $groupId');
|
|
||||||
|
|
||||||
final groupData = groupDoc.data() as Map<String, dynamic>;
|
final groupData = groupDoc.data() as Map<String, dynamic>;
|
||||||
final group = Group.fromMap(groupData, groupId);
|
final group = Group.fromMap(groupData, groupId);
|
||||||
|
|
||||||
final members = await getGroupMembers(groupId);
|
final members = await getGroupMembers(groupId);
|
||||||
print('${members.length} membres chargés pour $groupId');
|
|
||||||
|
|
||||||
userGroups.add(group.copyWith(members: members));
|
userGroups.add(group.copyWith(members: members));
|
||||||
} else {
|
} else {
|
||||||
print('✗ Utilisateur NON membre de $groupId');
|
_errorService.logInfo('group_repository.dart','Utilisateur NON membre de $groupId');
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('ERREUR groupe ${groupDoc.id}: $e');
|
_errorService.logError(e.toString(), stackTrace);
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print('===== Retour: ${userGroups.length} groupes =====');
|
|
||||||
return userGroups;
|
return userGroups;
|
||||||
})
|
})
|
||||||
.distinct((prev, next) {
|
.distinct((prev, next) {
|
||||||
// Comparer les listes pour éviter les doublons
|
// Comparer les listes pour éviter les doublons
|
||||||
if (prev.length != next.length) {
|
if (prev.length != next.length) {
|
||||||
print('>>> Changement détecté: ${prev.length} -> ${next.length} groupes');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,17 +81,10 @@ class GroupRepository {
|
|||||||
final identical = prevIds.difference(nextIds).isEmpty &&
|
final identical = prevIds.difference(nextIds).isEmpty &&
|
||||||
nextIds.difference(prevIds).isEmpty;
|
nextIds.difference(prevIds).isEmpty;
|
||||||
|
|
||||||
if (!identical) {
|
|
||||||
print('>>> Changement détecté: IDs différents');
|
|
||||||
} else {
|
|
||||||
print('>>> Données identiques, émission ignorée');
|
|
||||||
}
|
|
||||||
|
|
||||||
return identical;
|
return identical;
|
||||||
})
|
})
|
||||||
.handleError((error, stackTrace) {
|
.handleError((error, stackTrace) {
|
||||||
print('ERREUR stream: $error');
|
_errorService.logError(error, stackTrace);
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
return <Group>[];
|
return <Group>[];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -146,10 +125,7 @@ class GroupRepository {
|
|||||||
|
|
||||||
Future<List<GroupMember>> getGroupMembers(String groupId) async {
|
Future<List<GroupMember>> getGroupMembers(String groupId) async {
|
||||||
try {
|
try {
|
||||||
print('Chargement membres pour: $groupId');
|
|
||||||
final snapshot = await _membersCollection(groupId).get();
|
final snapshot = await _membersCollection(groupId).get();
|
||||||
print('${snapshot.docs.length} membres trouvés');
|
|
||||||
|
|
||||||
return snapshot.docs
|
return snapshot.docs
|
||||||
.map((doc) {
|
.map((doc) {
|
||||||
return GroupMember.fromMap(
|
return GroupMember.fromMap(
|
||||||
@@ -159,7 +135,6 @@ class GroupRepository {
|
|||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ERREUR getGroupMembers: $e');
|
|
||||||
throw Exception('Erreur lors de la récupération des membres: $e');
|
throw Exception('Erreur lors de la récupération des membres: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
|
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
|
final _errorService = ErrorService();
|
||||||
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
|
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
|
||||||
|
|
||||||
User? get currentUser => firebaseAuth.currentUser;
|
User? get currentUser => firebaseAuth.currentUser;
|
||||||
@@ -89,13 +91,13 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} on GoogleSignInException catch (e) {
|
} on GoogleSignInException catch (e) {
|
||||||
print('Erreur lors de l\'initialisation de Google Sign-In: $e');
|
_errorService.logError('Erreur Google Sign-In: $e', StackTrace.current);
|
||||||
rethrow;
|
rethrow;
|
||||||
} on FirebaseAuthException catch (e) {
|
} on FirebaseAuthException catch (e) {
|
||||||
print('Erreur Firebase lors de l\'initialisation de Google Sign-In: $e');
|
_errorService.logError('Erreur Firebase lors de l\'initialisation de Google Sign-In: $e', StackTrace.current);
|
||||||
rethrow;
|
rethrow;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur inconnue lors de l\'initialisation de Google Sign-In: $e');
|
_errorService.logError('Erreur inconnue lors de l\'initialisation de Google Sign-In: $e', StackTrace.current);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
lib/services/error_service.dart
Normal file
82
lib/services/error_service.dart
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../components/error/error_content.dart';
|
||||||
|
|
||||||
|
class ErrorService {
|
||||||
|
static final ErrorService _instance = ErrorService._internal();
|
||||||
|
factory ErrorService() => _instance;
|
||||||
|
ErrorService._internal();
|
||||||
|
|
||||||
|
// GlobalKey pour accéder au context depuis n'importe où
|
||||||
|
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
// Afficher une erreur en dialog
|
||||||
|
void showError({
|
||||||
|
required String message,
|
||||||
|
String title = 'Erreur',
|
||||||
|
VoidCallback? onRetry,
|
||||||
|
IconData icon = Icons.error_outline,
|
||||||
|
Color? iconColor,
|
||||||
|
}) {
|
||||||
|
final context = navigatorKey.currentContext;
|
||||||
|
if (context != null) {
|
||||||
|
showErrorDialog(
|
||||||
|
context,
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
icon: icon,
|
||||||
|
iconColor: iconColor,
|
||||||
|
onRetry: onRetry,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher une erreur en snackbar
|
||||||
|
void showSnackbar({
|
||||||
|
required String message,
|
||||||
|
VoidCallback? onRetry,
|
||||||
|
bool isError = true,
|
||||||
|
}) {
|
||||||
|
final context = navigatorKey.currentContext;
|
||||||
|
if (context != null) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: isError ? Colors.red[400] : Colors.green[600],
|
||||||
|
duration: const Duration(seconds: 4),
|
||||||
|
action: onRetry != null
|
||||||
|
? SnackBarAction(
|
||||||
|
label: 'Réessayer',
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: onRetry,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger dans la console (développement)
|
||||||
|
void logError(String source, dynamic error, [StackTrace? stackTrace]) {
|
||||||
|
print('═══════════════════════════════════');
|
||||||
|
print('❌ ERREUR dans $source');
|
||||||
|
print('Message: $error');
|
||||||
|
if (stackTrace != null) {
|
||||||
|
print('StackTrace: $stackTrace');
|
||||||
|
}
|
||||||
|
print('═══════════════════════════════════');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger une info (développement)
|
||||||
|
void logInfo(String source, String message) {
|
||||||
|
print('ℹ️ [$source] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger un succès
|
||||||
|
void logSuccess(String source, String message) {
|
||||||
|
print('✅ [$source] $message');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:travel_mate/data/models/group.dart';
|
import 'package:travel_mate/data/models/group.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
|
|
||||||
class GroupService {
|
class GroupService {
|
||||||
|
final _errorService = ErrorService();
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
Stream<List<Group>> getGroupsStream() {
|
Stream<List<Group>> getGroupsStream() {
|
||||||
@@ -17,7 +19,7 @@ class GroupService {
|
|||||||
await _firestore.collection('groups').add(group.toMap());
|
await _firestore.collection('groups').add(group.toMap());
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la création du groupe: $e');
|
_errorService.logError('Erreur lors de la création du groupe: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +29,7 @@ class GroupService {
|
|||||||
await _firestore.collection('groups').doc(group.id).update(group.toMap());
|
await _firestore.collection('groups').doc(group.id).update(group.toMap());
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la mise à jour du groupe: $e');
|
_errorService.logError('Erreur lors de la mise à jour du groupe: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +39,7 @@ class GroupService {
|
|||||||
await _firestore.collection('groups').doc(groupId).delete();
|
await _firestore.collection('groups').doc(groupId).delete();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la suppression du groupe: $e');
|
_errorService.logError('Erreur lors de la suppression du groupe: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,10 +51,9 @@ class GroupService {
|
|||||||
.where('members', arrayContains: userId)
|
.where('members', arrayContains: userId)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map((snapshot) {
|
.map((snapshot) {
|
||||||
print('Groupes trouvés pour l\'utilisateur $userId: ${snapshot.docs.length}');
|
|
||||||
return snapshot.docs.map((doc) {
|
return snapshot.docs.map((doc) {
|
||||||
final group = Group.fromMap(doc.data(), doc.id);
|
final group = Group.fromMap(doc.data(), doc.id);
|
||||||
print('Groupe: ${group.name}, Membres: ${group.members.length}');
|
_errorService.logError('Groupe: ${group.name}, Membres: ${group.members.length}', StackTrace.current);
|
||||||
return group;
|
return group;
|
||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
@@ -65,6 +66,4 @@ class GroupService {
|
|||||||
Future<void> addMemberToGroup(String groupId, String memberId) async {
|
Future<void> addMemberToGroup(String groupId, String memberId) async {
|
||||||
// TODO: Implémenter l'ajout d'un membre à un groupe
|
// TODO: Implémenter l'ajout d'un membre à un groupe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import '../data/models/trip.dart';
|
import '../data/models/trip.dart';
|
||||||
|
|
||||||
class TripService {
|
class TripService {
|
||||||
|
final _errorService = ErrorService();
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
static const String _tripsCollection = 'trips';
|
static const String _tripsCollection = 'trips';
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ class TripService {
|
|||||||
return Trip.fromMap({...data, 'id': doc.id});
|
return Trip.fromMap({...data, 'id': doc.id});
|
||||||
}).toList();
|
}).toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors du chargement des voyages: $e');
|
_errorService.logError('Erreur lors du chargement des voyages: $e', StackTrace.current);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,16 +41,13 @@ class TripService {
|
|||||||
await _firestore.collection(_tripsCollection).add(tripData);
|
await _firestore.collection(_tripsCollection).add(tripData);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de l\'ajout du voyage: $e');
|
_errorService.logError('Erreur lors de l\'ajout du voyage: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream pour écouter les voyages d'un utilisateur en temps réel
|
// Stream pour écouter les voyages d'un utilisateur en temps réel
|
||||||
Stream<List<Trip>> getTripsStreamByUser(String userId, String userEmail) {
|
Stream<List<Trip>> getTripsStreamByUser(String userId, String userEmail) {
|
||||||
print('=== STREAM CRÉÉ ===');
|
|
||||||
print('UserId: $userId');
|
|
||||||
|
|
||||||
return _firestore
|
return _firestore
|
||||||
.collection(_tripsCollection)
|
.collection(_tripsCollection)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
@@ -88,8 +87,7 @@ class TripService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('Erreur lors du traitement du document ${doc.id}: $e');
|
_errorService.logError('Erreur lors du traitement du document ${doc.id}: $e', stackTrace);
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,15 +96,14 @@ class TripService {
|
|||||||
try {
|
try {
|
||||||
return b.createdAt.compareTo(a.createdAt);
|
return b.createdAt.compareTo(a.createdAt);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors du tri: $e');
|
_errorService.logError('Erreur lors du tri: $e', StackTrace.current);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return trips;
|
return trips;
|
||||||
}).handleError((error, stackTrace) {
|
}).handleError((error, stackTrace) {
|
||||||
print('Erreur dans le stream: $error');
|
_errorService.logError('Erreur dans le stream: $error', stackTrace);
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
return <Trip>[];
|
return <Trip>[];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -130,7 +127,7 @@ class TripService {
|
|||||||
trips.add(trip);
|
trips.add(trip);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la conversion du voyage créé ${doc.id}: $e');
|
_errorService.logError('Erreur lors de la conversion du voyage créé ${doc.id}: $e', StackTrace.current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +141,7 @@ class TripService {
|
|||||||
});
|
});
|
||||||
return trips;
|
return trips;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération des voyages: $e');
|
_errorService.logError('Erreur lors de la récupération des voyages: $e', StackTrace.current);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,8 +180,7 @@ class TripService {
|
|||||||
return trip;
|
return trip;
|
||||||
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('Erreur lors de la conversion du document $docId: $e');
|
_errorService.logError('Erreur lors de la conversion du document $docId: $e', stackTrace);
|
||||||
print('StackTrace: $stackTrace');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +198,7 @@ class TripService {
|
|||||||
.update(tripData);
|
.update(tripData);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la mise à jour du voyage: $e');
|
_errorService.logError('Erreur lors de la mise à jour du voyage: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +209,7 @@ class TripService {
|
|||||||
await _firestore.collection(_tripsCollection).doc(tripId).delete();
|
await _firestore.collection(_tripsCollection).doc(tripId).delete();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la suppression du voyage: $e');
|
_errorService.logError('Erreur lors de la suppression du voyage: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,7 +228,7 @@ class TripService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération du voyage: $e');
|
_errorService.logError('Erreur lors de la récupération du voyage: $e', StackTrace.current);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +242,7 @@ class TripService {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de l\'ajout du participant: $e');
|
_errorService.logError('Erreur lors de l\'ajout du participant: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +256,7 @@ class TripService {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors du retrait du participant: $e');
|
_errorService.logError('Erreur lors du retrait du participant: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import '../blocs/user/user_state.dart';
|
import '../blocs/user/user_state.dart';
|
||||||
|
|
||||||
class UserService {
|
class UserService {
|
||||||
|
final _errorService = ErrorService();
|
||||||
final FirebaseFirestore _firestore;
|
final FirebaseFirestore _firestore;
|
||||||
final FirebaseAuth _auth;
|
final FirebaseAuth _auth;
|
||||||
static const String _usersCollection = 'users';
|
static const String _usersCollection = 'users';
|
||||||
@@ -32,7 +34,7 @@ class UserService {
|
|||||||
.set(user.toJson());
|
.set(user.toJson());
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la création de l\'utilisateur: $e');
|
_errorService.logError('Erreur lors de la création de l\'utilisateur: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,7 @@ class UserService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération de l\'utilisateur: $e');
|
_errorService.logError('Erreur lors de la récupération de l\'utilisateur: $e', StackTrace.current);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +78,7 @@ class UserService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération de l\'utilisateur par email: $e');
|
_errorService.logError('Erreur lors de la récupération de l\'utilisateur par email: $e', StackTrace.current);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +97,7 @@ class UserService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération de l\'ID utilisateur: $e');
|
_errorService.logError('Erreur lors de la récupération de l\'ID utilisateur: $e', StackTrace.current);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,7 @@ class UserService {
|
|||||||
.update(userData);
|
.update(userData);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la mise à jour de l\'utilisateur: $e');
|
_errorService.logError('Erreur lors de la mise à jour de l\'utilisateur: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,7 @@ class UserService {
|
|||||||
.delete();
|
.delete();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la suppression de l\'utilisateur: $e');
|
_errorService.logError('Erreur lors de la suppression de l\'utilisateur: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +141,7 @@ class UserService {
|
|||||||
|
|
||||||
return querySnapshot.docs.isNotEmpty;
|
return querySnapshot.docs.isNotEmpty;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la vérification de l\'email: $e');
|
_errorService.logError('Erreur lors de la vérification de l\'email: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +173,7 @@ class UserService {
|
|||||||
|
|
||||||
return users;
|
return users;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération des utilisateurs: $e');
|
_errorService.logError('Erreur lors de la récupération des utilisateurs: $e', StackTrace.current);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,7 +192,7 @@ class UserService {
|
|||||||
});
|
});
|
||||||
}).toList();
|
}).toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la récupération de tous les utilisateurs: $e');
|
_errorService.logError('Erreur lors de la récupération de tous les utilisateurs: $e', StackTrace.current);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +256,7 @@ class UserService {
|
|||||||
|
|
||||||
return usersMap.values.toList();
|
return usersMap.values.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la recherche d\'utilisateurs: $e');
|
_errorService.logError('Erreur lors de la recherche d\'utilisateurs: $e', StackTrace.current);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +272,7 @@ class UserService {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la mise à jour de la dernière connexion: $e');
|
_errorService.logError('Erreur lors de la mise à jour de la dernière connexion: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,7 +286,7 @@ class UserService {
|
|||||||
.get();
|
.get();
|
||||||
return doc.exists;
|
return doc.exists;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la vérification de l\'existence de l\'utilisateur: $e');
|
_errorService.logError('Erreur lors de la vérification de l\'existence de l\'utilisateur: $e', StackTrace.current);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user