feat: Integrate ErrorService for improved error handling and add imageUrl field to Trip model
This commit is contained in:
@@ -29,7 +29,8 @@ class HomeContent extends StatefulWidget {
|
|||||||
State<HomeContent> createState() => _HomeContentState();
|
State<HomeContent> createState() => _HomeContentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClientMixin {
|
class _HomeContentState extends State<HomeContent>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
/// Preserves widget state when switching between tabs
|
/// Preserves widget state when switching between tabs
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@@ -56,7 +57,9 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
final userState = context.read<UserBloc>().state;
|
final userState = context.read<UserBloc>().state;
|
||||||
if (userState is UserLoaded) {
|
if (userState is UserLoaded) {
|
||||||
_hasLoadedTrips = true;
|
_hasLoadedTrips = true;
|
||||||
context.read<TripBloc>().add(LoadTripsByUserId(userId: userState.user.id));
|
context.read<TripBloc>().add(
|
||||||
|
LoadTripsByUserId(userId: userState.user.id),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,11 +71,7 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
return BlocBuilder<UserBloc, UserState>(
|
return BlocBuilder<UserBloc, UserState>(
|
||||||
builder: (context, userState) {
|
builder: (context, userState) {
|
||||||
if (userState is UserLoading) {
|
if (userState is UserLoading) {
|
||||||
return Scaffold(
|
return Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||||
body: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userState is UserError) {
|
if (userState is UserError) {
|
||||||
@@ -91,11 +90,7 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userState is! UserLoaded) {
|
if (userState is! UserLoaded) {
|
||||||
return Scaffold(
|
return Scaffold(body: Center(child: Text('Veuillez vous connecter')));
|
||||||
body: Center(
|
|
||||||
child: Text('Veuillez vous connecter'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final user = userState.user;
|
final user = userState.user;
|
||||||
@@ -137,7 +132,9 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
context.read<TripBloc>().add(LoadTripsByUserId(userId: user.id));
|
context.read<TripBloc>().add(
|
||||||
|
LoadTripsByUserId(userId: user.id),
|
||||||
|
);
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
await Future.delayed(Duration(milliseconds: 500));
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -192,7 +189,9 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
|
|
||||||
final result = await Navigator.push(
|
final result = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const CreateTripContent()),
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const CreateTripContent(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == true && mounted) {
|
if (result == true && mounted) {
|
||||||
@@ -203,7 +202,8 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
floatingActionButtonLocation:
|
||||||
|
FloatingActionButtonLocation.endFloat,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -274,9 +274,7 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTripsList(List<Trip> trips) {
|
Widget _buildTripsList(List<Trip> trips) {
|
||||||
return Column(
|
return Column(children: trips.map((trip) => _buildTripCard(trip)).toList());
|
||||||
children: trips.map((trip) => _buildTripCard(trip)).toList(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTripCard(Trip trip) {
|
Widget _buildTripCard(Trip trip) {
|
||||||
@@ -287,37 +285,59 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.only(bottom: 12),
|
margin: EdgeInsets.only(bottom: 12),
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
borderRadius: BorderRadius.circular(12),
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Image en haut de la carte
|
||||||
|
Container(
|
||||||
|
height: 200,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(12),
|
||||||
|
topRight: Radius.circular(12),
|
||||||
),
|
),
|
||||||
child: InkWell(
|
color: Colors.grey[300],
|
||||||
onTap: () async {
|
),
|
||||||
final result = await Navigator.push(
|
child: ClipRRect(
|
||||||
context,
|
borderRadius: BorderRadius.only(
|
||||||
MaterialPageRoute(
|
topLeft: Radius.circular(12),
|
||||||
builder: (context) => ShowTripDetailsContent(trip: trip),
|
topRight: Radius.circular(12),
|
||||||
|
),
|
||||||
|
child: trip.imageUrl != null && trip.imageUrl!.isNotEmpty
|
||||||
|
? Image.network(
|
||||||
|
trip.imageUrl!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
child: Icon(
|
||||||
|
Icons.image_not_supported,
|
||||||
|
size: 50,
|
||||||
|
color: Colors.grey[600],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == true && mounted) {
|
|
||||||
final userState = context.read<UserBloc>().state;
|
|
||||||
if (userState is UserLoaded) {
|
|
||||||
context.read<TripBloc>().add(LoadTripsByUserId(userId: userState.user.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(12),
|
)
|
||||||
child: Padding(
|
: Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
child: Icon(
|
||||||
|
Icons.travel_explore,
|
||||||
|
size: 50,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Contenu de la carte
|
||||||
|
Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
// Nom du voyage
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
Text(
|
||||||
trip.title,
|
trip.title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -326,102 +346,125 @@ class _HomeContentState extends State<HomeContent> with AutomaticKeepAliveClient
|
|||||||
color: textColor,
|
color: textColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 4),
|
|
||||||
|
SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Section dates, participants et bouton
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Colonne gauche : dates et participants
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Dates
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.location_on, size: 16, color: subtextColor),
|
Icon(
|
||||||
SizedBox(width: 4),
|
Icons.calendar_today,
|
||||||
Text(
|
size: 16,
|
||||||
trip.location,
|
color: subtextColor,
|
||||||
style: TextStyle(color: subtextColor),
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _getStatusColor(trip).withValues(alpha: 0.2),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
_getStatusText(trip),
|
|
||||||
style: TextStyle(
|
|
||||||
color: _getStatusColor(trip),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.calendar_today, size: 16, color: subtextColor),
|
|
||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'${_formatDate(trip.startDate)} - ${_formatDate(trip.endDate)}',
|
'${_formatDate(trip.startDate)} - ${_formatDate(trip.endDate)}',
|
||||||
style: TextStyle(fontSize: 14, color: subtextColor),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: subtextColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (trip.budget != null) ...[
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.euro, size: 16, color: subtextColor),
|
|
||||||
SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'${trip.budget!.toStringAsFixed(2)} €',
|
|
||||||
style: TextStyle(fontSize: 14, color: subtextColor),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Nombre de participants
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.people, size: 16, color: subtextColor),
|
Icon(Icons.people, size: 16, color: subtextColor),
|
||||||
SizedBox(width: 4),
|
SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'${trip.participants.length} participant${trip.participants.length > 1 ? 's' : ''}',
|
'${trip.participants.length} participant${trip.participants.length > 1 ? 's' : ''}',
|
||||||
style: TextStyle(fontSize: 14, color: subtextColor),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: subtextColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Bouton "Voir" à droite
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
ShowTripDetailsContent(trip: trip),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == true && mounted) {
|
||||||
|
final userState = context.read<UserBloc>().state;
|
||||||
|
if (userState is UserLoaded) {
|
||||||
|
context.read<TripBloc>().add(
|
||||||
|
LoadTripsByUserId(userId: userState.user.id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onPrimary,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text('Voir', style: TextStyle(fontSize: 14)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getStatusColor(Trip trip) {
|
// Color _getStatusColor(Trip trip) {
|
||||||
final now = DateTime.now();
|
// final now = DateTime.now();
|
||||||
if (now.isBefore(trip.startDate)) {
|
// if (now.isBefore(trip.startDate)) {
|
||||||
return Colors.blue;
|
// return Colors.blue;
|
||||||
} else if (now.isAfter(trip.endDate)) {
|
// } else if (now.isAfter(trip.endDate)) {
|
||||||
return Colors.grey;
|
// return Colors.grey;
|
||||||
} else {
|
// } else {
|
||||||
return Colors.green;
|
// return Colors.green;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
String _getStatusText(Trip trip) {
|
// String _getStatusText(Trip trip) {
|
||||||
final now = DateTime.now();
|
// final now = DateTime.now();
|
||||||
if (now.isBefore(trip.startDate)) {
|
// if (now.isBefore(trip.startDate)) {
|
||||||
return 'À venir';
|
// return 'À venir';
|
||||||
} else if (now.isAfter(trip.endDate)) {
|
// } else if (now.isAfter(trip.endDate)) {
|
||||||
return 'Terminé';
|
// return 'Terminé';
|
||||||
} else {
|
// } else {
|
||||||
return 'En cours';
|
// return 'En cours';
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
String _formatDate(DateTime date) {
|
String _formatDate(DateTime date) {
|
||||||
return '${date.day}/${date.month}/${date.year}';
|
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:travel_mate/blocs/trip/trip_bloc.dart';
|
|||||||
import 'package:travel_mate/blocs/trip/trip_event.dart';
|
import 'package:travel_mate/blocs/trip/trip_event.dart';
|
||||||
import 'package:travel_mate/components/home/create_trip_content.dart';
|
import 'package:travel_mate/components/home/create_trip_content.dart';
|
||||||
import 'package:travel_mate/models/trip.dart';
|
import 'package:travel_mate/models/trip.dart';
|
||||||
|
import 'package:travel_mate/services/error_service.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart'; // Ajouter cet import
|
import 'package:url_launcher/url_launcher.dart'; // Ajouter cet import
|
||||||
import 'package:travel_mate/components/map/map_content.dart'; // Ajouter cet import si la page carte existe
|
import 'package:travel_mate/components/map/map_content.dart'; // Ajouter cet import si la page carte existe
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ class ShowTripDetailsContent extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
||||||
|
final ErrorService _errorService = ErrorService();
|
||||||
|
|
||||||
// Méthode pour ouvrir la carte interne
|
// Méthode pour ouvrir la carte interne
|
||||||
void _openInternalMap() {
|
void _openInternalMap() {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -45,13 +48,9 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
} else {
|
} else {
|
||||||
// Si rien ne marche, afficher un message d'erreur
|
// Si rien ne marche, afficher un message d'erreur
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
_errorService.showError(
|
||||||
const SnackBar(
|
message:
|
||||||
content: Text(
|
'Impossible d\'ouvrir Google Maps, veuillez vérifier que l\'application est installée.',
|
||||||
'Impossible d\'ouvrir Google Maps. Vérifiez que l\'application est installée.',
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ class Trip {
|
|||||||
/// Current status of the trip (e.g., 'draft', 'active', 'completed').
|
/// Current status of the trip (e.g., 'draft', 'active', 'completed').
|
||||||
final String status;
|
final String status;
|
||||||
|
|
||||||
|
final String? imageUrl;
|
||||||
|
|
||||||
/// Creates a new [Trip] instance.
|
/// Creates a new [Trip] instance.
|
||||||
///
|
///
|
||||||
/// Most fields are required except [id] and [budget].
|
/// Most fields are required except [id] and [budget].
|
||||||
@@ -60,6 +62,7 @@ class Trip {
|
|||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
this.status = 'draft',
|
this.status = 'draft',
|
||||||
|
this.imageUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOUVELLE MÉTHODE HELPER pour convertir n'importe quel format de date
|
// NOUVELLE MÉTHODE HELPER pour convertir n'importe quel format de date
|
||||||
@@ -106,6 +109,7 @@ class Trip {
|
|||||||
createdAt: _parseDateTime(map['createdAt']),
|
createdAt: _parseDateTime(map['createdAt']),
|
||||||
updatedAt: _parseDateTime(map['updatedAt']),
|
updatedAt: _parseDateTime(map['updatedAt']),
|
||||||
status: map['status'] as String? ?? 'draft',
|
status: map['status'] as String? ?? 'draft',
|
||||||
|
imageUrl: map['imageUrl'] as String?,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -126,6 +130,7 @@ class Trip {
|
|||||||
'createdAt': Timestamp.fromDate(createdAt),
|
'createdAt': Timestamp.fromDate(createdAt),
|
||||||
'updatedAt': Timestamp.fromDate(updatedAt),
|
'updatedAt': Timestamp.fromDate(updatedAt),
|
||||||
'status': status,
|
'status': status,
|
||||||
|
'imageUrl': imageUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +153,7 @@ class Trip {
|
|||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
String? status,
|
String? status,
|
||||||
|
String? imageUrl,
|
||||||
}) {
|
}) {
|
||||||
return Trip(
|
return Trip(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -162,6 +168,7 @@ class Trip {
|
|||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
|
imageUrl: imageUrl ?? this.imageUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user