264 lines
8.4 KiB
Dart
264 lines
8.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:travel_mate/models/trip.dart';
|
|
import 'package:travel_mate/services/place_image_service.dart';
|
|
import 'package:travel_mate/repositories/trip_repository.dart';
|
|
|
|
class TripCard extends StatefulWidget {
|
|
final Trip trip;
|
|
final VoidCallback? onTap;
|
|
|
|
const TripCard({
|
|
super.key,
|
|
required this.trip,
|
|
this.onTap,
|
|
});
|
|
|
|
@override
|
|
State<TripCard> createState() => _TripCardState();
|
|
}
|
|
|
|
class _TripCardState extends State<TripCard> {
|
|
final PlaceImageService _placeImageService = PlaceImageService();
|
|
final TripRepository _tripRepository = TripRepository();
|
|
String? _currentImageUrl;
|
|
bool _isLoadingImage = false;
|
|
bool _hasTriedLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_currentImageUrl = widget.trip.imageUrl;
|
|
|
|
// Si aucune image n'est disponible, essayer de la charger
|
|
if (_currentImageUrl == null || _currentImageUrl!.isEmpty) {
|
|
_loadImageForTrip();
|
|
}
|
|
}
|
|
|
|
Future<void> _loadImageForTrip() async {
|
|
if (_hasTriedLoading || _isLoadingImage) return;
|
|
|
|
setState(() {
|
|
_isLoadingImage = true;
|
|
_hasTriedLoading = true;
|
|
});
|
|
|
|
try {
|
|
|
|
// D'abord vérifier si une image existe déjà dans le Storage
|
|
String? imageUrl = await _placeImageService.getExistingImageUrl(widget.trip.location);
|
|
|
|
// Si aucune image n'existe, en télécharger une nouvelle
|
|
if (imageUrl == null) {
|
|
imageUrl = await _placeImageService.getPlaceImageUrl(widget.trip.location);
|
|
}
|
|
|
|
if (mounted && imageUrl != null) {
|
|
setState(() {
|
|
_currentImageUrl = imageUrl;
|
|
_isLoadingImage = false;
|
|
});
|
|
|
|
// Mettre à jour le voyage dans la base de données avec l'imageUrl
|
|
_updateTripWithImage(imageUrl);
|
|
} else {
|
|
setState(() {
|
|
_isLoadingImage = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoadingImage = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _updateTripWithImage(String imageUrl) async {
|
|
try {
|
|
if (widget.trip.id != null) {
|
|
// Créer une copie du voyage avec la nouvelle imageUrl
|
|
final updatedTrip = widget.trip.copyWith(
|
|
imageUrl: imageUrl,
|
|
updatedAt: DateTime.now(),
|
|
);
|
|
|
|
// Mettre à jour dans la base de données
|
|
await _tripRepository.updateTrip(widget.trip.id!, updatedTrip);
|
|
}
|
|
} catch (e) {
|
|
// En cas d'erreur, on continue sans échec - l'image reste affichée localement
|
|
}
|
|
}
|
|
|
|
Widget _buildImageWidget() {
|
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
if (_isLoadingImage) {
|
|
return Container(
|
|
color: isDarkMode ? Colors.grey[700] : Colors.grey[200],
|
|
child: const Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
CircularProgressIndicator(),
|
|
SizedBox(height: 8),
|
|
Text('Chargement de l\'image...'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (_currentImageUrl != null && _currentImageUrl!.isNotEmpty) {
|
|
return CachedNetworkImage(
|
|
imageUrl: _currentImageUrl!,
|
|
fit: BoxFit.cover,
|
|
placeholder: (context, url) => Container(
|
|
color: Colors.grey[200],
|
|
child: const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
),
|
|
errorWidget: (context, url, error) => _buildPlaceholderImage(isDarkMode),
|
|
);
|
|
}
|
|
|
|
return _buildPlaceholderImage(isDarkMode);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
|
final cardColor = isDarkMode ? Colors.grey[800] : Colors.white;
|
|
final textColor = isDarkMode ? Colors.white : Colors.black;
|
|
final secondaryTextColor = isDarkMode ? Colors.white70 : Colors.grey[600];
|
|
|
|
return Card(
|
|
elevation: 4,
|
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
color: cardColor,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: widget.onTap,
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Image du voyage
|
|
ClipRRect(
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
|
child: SizedBox(
|
|
height: 200,
|
|
width: double.infinity,
|
|
child: _buildImageWidget(),
|
|
),
|
|
),
|
|
// Informations du voyage
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Titre du voyage
|
|
Text(
|
|
widget.trip.title,
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: textColor,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// Dates, participants et bouton voir
|
|
Row(
|
|
children: [
|
|
// Colonne avec dates et participants
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Dates
|
|
Text(
|
|
widget.trip.formattedDates,
|
|
style: TextStyle(
|
|
color: secondaryTextColor,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
// Participants
|
|
Text(
|
|
'${widget.trip.totalParticipants - 1} participant${widget.trip.totalParticipants > 1 ? 's' : ''}',
|
|
style: TextStyle(
|
|
color: secondaryTextColor,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Bouton Voir
|
|
ElevatedButton(
|
|
onPressed: widget.onTap,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
),
|
|
child: const Text(
|
|
'Voir',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPlaceholderImage(bool isDarkMode) {
|
|
return Container(
|
|
color: isDarkMode ? Colors.grey[700] : Colors.grey[200],
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.location_city,
|
|
size: 48,
|
|
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Aucune image',
|
|
style: TextStyle(
|
|
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |