1438 lines
52 KiB
Dart
1438 lines
52 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import '../../blocs/activity/activity_bloc.dart';
|
||
import '../../blocs/activity/activity_event.dart';
|
||
import '../../blocs/activity/activity_state.dart';
|
||
import '../../models/trip.dart';
|
||
import '../../models/activity.dart';
|
||
import '../activities/add_activity_bottom_sheet.dart';
|
||
|
||
class ActivitiesPage extends StatefulWidget {
|
||
final Trip trip;
|
||
|
||
const ActivitiesPage({
|
||
super.key,
|
||
required this.trip,
|
||
});
|
||
|
||
@override
|
||
State<ActivitiesPage> createState() => _ActivitiesPageState();
|
||
}
|
||
|
||
class _ActivitiesPageState extends State<ActivitiesPage>
|
||
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||
late TabController _tabController;
|
||
final TextEditingController _searchController = TextEditingController();
|
||
String _selectedCategory = 'Toutes les catégories';
|
||
String _selectedPrice = 'Prix';
|
||
String _selectedRating = 'Note';
|
||
|
||
// Cache pour éviter de recharger les données
|
||
bool _activitiesLoaded = false;
|
||
bool _googleSearchPerformed = false;
|
||
|
||
// Variables pour stocker les activités localement
|
||
List<Activity> _tripActivities = [];
|
||
List<Activity> _approvedActivities = [];
|
||
bool _isLoadingTripActivities = false;
|
||
int _totalGoogleActivitiesRequested = 0; // Compteur pour les recherches progressives
|
||
bool _autoReloadInProgress = false; // Protection contre les rechargements en boucle
|
||
int _lastAutoReloadTriggerCount = 0; // Éviter de redéclencher pour le même nombre
|
||
|
||
@override
|
||
bool get wantKeepAlive => true; // Maintient l'état de la page
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_tabController = TabController(length: 3, vsync: this);
|
||
|
||
// Charger les activités au démarrage
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_loadActivitiesIfNeeded();
|
||
});
|
||
|
||
// Écouter les changements d'onglets
|
||
_tabController.addListener(() {
|
||
if (_tabController.indexIsChanging) {
|
||
_handleTabChange();
|
||
}
|
||
});
|
||
}
|
||
|
||
@override
|
||
void didChangeDependencies() {
|
||
super.didChangeDependencies();
|
||
// Recharger si nécessaire quand on revient sur la page
|
||
if (!_activitiesLoaded) {
|
||
_loadActivitiesIfNeeded();
|
||
}
|
||
}
|
||
|
||
void _loadActivitiesIfNeeded() {
|
||
final state = context.read<ActivityBloc>().state;
|
||
if (state is! ActivityLoaded || !_activitiesLoaded) {
|
||
_loadActivities();
|
||
}
|
||
}
|
||
|
||
void _handleTabChange() {
|
||
// Si on va sur l'onglet suggestions Google et qu'aucune recherche n'a été faite
|
||
if (_tabController.index == 2 && !_googleSearchPerformed) {
|
||
_searchGoogleActivities();
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_tabController.dispose();
|
||
_searchController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _loadActivities() {
|
||
setState(() {
|
||
_isLoadingTripActivities = true;
|
||
});
|
||
context.read<ActivityBloc>().add(LoadActivities(widget.trip.id!));
|
||
_activitiesLoaded = true;
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
super.build(context); // Nécessaire pour AutomaticKeepAliveClientMixin
|
||
final theme = Theme.of(context);
|
||
|
||
return BlocListener<ActivityBloc, ActivityState>(
|
||
listener: (context, state) {
|
||
if (state is ActivityError) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(state.message),
|
||
backgroundColor: Colors.red,
|
||
action: SnackBarAction(
|
||
label: 'Réessayer',
|
||
textColor: Colors.white,
|
||
onPressed: () {
|
||
if (_tabController.index == 2) {
|
||
_searchGoogleActivities();
|
||
} else {
|
||
_loadActivities();
|
||
}
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
if (state is ActivityLoaded) {
|
||
print('✅ Activités chargées: ${state.activities.length}');
|
||
// Stocker les activités localement
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
setState(() {
|
||
_tripActivities = state.activities;
|
||
_approvedActivities = state.activities.where((a) => a.totalVotes > 0).toList();
|
||
_isLoadingTripActivities = false;
|
||
});
|
||
|
||
print('🔄 [ActivityLoaded] Activités du voyage mises à jour: ${_tripActivities.length}');
|
||
// Vérifier si on a besoin de charger plus d'activités dans les suggestions
|
||
Future.delayed(const Duration(milliseconds: 500), () {
|
||
print('🚀 [ActivityLoaded] Déclenchement de la vérification auto-reload');
|
||
_checkAndLoadMoreActivitiesIfNeeded();
|
||
});
|
||
});
|
||
}
|
||
|
||
if (state is ActivitySearchResults) {
|
||
print('🔍 Résultats Google: ${state.searchResults.length}');
|
||
// Déclencher l'auto-reload uniquement pour la recherche initiale (6 résultats)
|
||
// et pas pour les rechargements automatiques
|
||
if (state.searchResults.length <= 6 && !_autoReloadInProgress) {
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
print('🎯 [ActivitySearchResults] Première recherche avec peu de résultats, vérification auto-reload');
|
||
Future.delayed(const Duration(milliseconds: 500), () {
|
||
_checkAndLoadMoreActivitiesIfNeeded();
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
if (state is ActivityVoteRecorded) {
|
||
print('<EFBFBD>️ Vote enregistré pour activité: ${state.activityId}');
|
||
// Recharger les activités du voyage pour mettre à jour les votes
|
||
_loadActivities();
|
||
}
|
||
|
||
if (state is ActivityAdded) {
|
||
print('✅ Activité ajoutée avec succès: ${state.activity.name}');
|
||
// Recharger automatiquement les activités du voyage
|
||
_loadActivities();
|
||
}
|
||
},
|
||
child: Scaffold(
|
||
backgroundColor: theme.colorScheme.surface,
|
||
appBar: AppBar(
|
||
title: Text(
|
||
'Voyage à ${widget.trip.location}',
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
color: theme.colorScheme.onSurface,
|
||
),
|
||
overflow: TextOverflow.ellipsis,
|
||
maxLines: 1,
|
||
),
|
||
backgroundColor: theme.colorScheme.surface,
|
||
elevation: 0,
|
||
foregroundColor: theme.colorScheme.onSurface,
|
||
actions: [
|
||
IconButton(
|
||
icon: const Icon(Icons.add),
|
||
onPressed: _showAddActivityBottomSheet,
|
||
),
|
||
],
|
||
),
|
||
body: Column(
|
||
children: [
|
||
// Barre de recherche
|
||
_buildSearchBar(theme),
|
||
|
||
// Filtres
|
||
_buildFilters(theme),
|
||
|
||
// Onglets de catégories
|
||
_buildCategoryTabs(theme),
|
||
|
||
// Contenu des onglets
|
||
Expanded(
|
||
child: TabBarView(
|
||
controller: _tabController,
|
||
children: [
|
||
_buildTripActivitiesTab(),
|
||
_buildApprovedActivitiesTab(),
|
||
_buildGoogleSuggestionsTab(),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSearchBar(ThemeData theme) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
color: theme.colorScheme.surfaceContainerHighest.withOpacity(0.3),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: TextField(
|
||
controller: _searchController,
|
||
decoration: InputDecoration(
|
||
hintText: 'Rechercher restaurants, musées...',
|
||
hintStyle: TextStyle(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||
),
|
||
prefixIcon: Icon(
|
||
Icons.search,
|
||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||
),
|
||
border: InputBorder.none,
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 16,
|
||
vertical: 12,
|
||
),
|
||
),
|
||
onChanged: (value) {
|
||
// TODO: Implémenter la recherche
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildFilters(ThemeData theme) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildFilterButton(
|
||
theme,
|
||
_selectedCategory,
|
||
Icons.category,
|
||
() => _showCategoryFilter(),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
_buildFilterButton(
|
||
theme,
|
||
_selectedPrice,
|
||
Icons.euro,
|
||
() => _showPriceFilter(),
|
||
),
|
||
const SizedBox(width: 12),
|
||
_buildFilterButton(
|
||
theme,
|
||
_selectedRating,
|
||
Icons.star,
|
||
() => _showRatingFilter(),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildFilterButton(
|
||
ThemeData theme,
|
||
String text,
|
||
IconData icon,
|
||
VoidCallback onPressed,
|
||
) {
|
||
return GestureDetector(
|
||
onTap: onPressed,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
border: Border.all(
|
||
color: theme.colorScheme.outline.withOpacity(0.5),
|
||
),
|
||
borderRadius: BorderRadius.circular(20),
|
||
color: theme.colorScheme.surface,
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(
|
||
icon,
|
||
size: 16,
|
||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||
),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
text,
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCategoryTabs(ThemeData theme) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
color: theme.colorScheme.surfaceContainerHighest.withOpacity(0.3),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: TabBar(
|
||
controller: _tabController,
|
||
labelColor: Colors.white,
|
||
unselectedLabelColor: theme.colorScheme.onSurface.withOpacity(0.7),
|
||
indicator: BoxDecoration(
|
||
color: theme.colorScheme.primary,
|
||
borderRadius: BorderRadius.circular(6),
|
||
),
|
||
indicatorSize: TabBarIndicatorSize.tab,
|
||
dividerColor: Colors.transparent,
|
||
tabs: const [
|
||
Tab(text: 'Activités du voyage'),
|
||
Tab(text: 'Activités approuvées'),
|
||
Tab(text: 'Suggestions Google'),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showCategoryFilter() {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
builder: (context) => _buildCategoryFilterSheet(),
|
||
);
|
||
}
|
||
|
||
void _showPriceFilter() {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
builder: (context) => _buildPriceFilterSheet(),
|
||
);
|
||
}
|
||
|
||
void _showRatingFilter() {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
builder: (context) => _buildRatingFilterSheet(),
|
||
);
|
||
}
|
||
|
||
Widget _buildCategoryFilterSheet() {
|
||
final theme = Theme.of(context);
|
||
final categories = [
|
||
'Toutes les catégories',
|
||
...ActivityCategory.values.map((e) => e.displayName),
|
||
];
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
constraints: BoxConstraints(
|
||
maxHeight: MediaQuery.of(context).size.height * 0.7, // Limite à 70% de l'écran
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Catégories',
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Flexible(
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
children: categories.map((category) => ListTile(
|
||
title: Text(category),
|
||
onTap: () {
|
||
setState(() {
|
||
_selectedCategory = category;
|
||
});
|
||
Navigator.pop(context);
|
||
_applyFilters();
|
||
},
|
||
trailing: _selectedCategory == category
|
||
? Icon(Icons.check, color: theme.colorScheme.primary)
|
||
: null,
|
||
)).toList(),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPriceFilterSheet() {
|
||
final theme = Theme.of(context);
|
||
final prices = ['Prix', 'Gratuit', 'Bon marché', 'Modéré', 'Cher', 'Très cher'];
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
constraints: BoxConstraints(
|
||
maxHeight: MediaQuery.of(context).size.height * 0.7,
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Niveau de prix',
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Flexible(
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
children: prices.map((price) => ListTile(
|
||
title: Text(price),
|
||
onTap: () {
|
||
setState(() {
|
||
_selectedPrice = price;
|
||
});
|
||
Navigator.pop(context);
|
||
_applyFilters();
|
||
},
|
||
trailing: _selectedPrice == price
|
||
? Icon(Icons.check, color: theme.colorScheme.primary)
|
||
: null,
|
||
)).toList(),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildRatingFilterSheet() {
|
||
final theme = Theme.of(context);
|
||
final ratings = ['Note', '4+ étoiles', '3+ étoiles', '2+ étoiles', '1+ étoiles'];
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
constraints: BoxConstraints(
|
||
maxHeight: MediaQuery.of(context).size.height * 0.7,
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Note minimale',
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Flexible(
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
children: ratings.map((rating) => ListTile(
|
||
title: Text(rating),
|
||
onTap: () {
|
||
setState(() {
|
||
_selectedRating = rating;
|
||
});
|
||
Navigator.pop(context);
|
||
_applyFilters();
|
||
},
|
||
trailing: _selectedRating == rating
|
||
? Icon(Icons.check, color: theme.colorScheme.primary)
|
||
: null,
|
||
)).toList(),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
void _applyFilters() {
|
||
String? category = _selectedCategory == 'Toutes les catégories' ? null : _selectedCategory;
|
||
double? minRating = _getMinRatingFromString(_selectedRating);
|
||
|
||
context.read<ActivityBloc>().add(FilterActivities(
|
||
category: category,
|
||
minRating: minRating,
|
||
));
|
||
}
|
||
|
||
double? _getMinRatingFromString(String rating) {
|
||
switch (rating) {
|
||
case '4+ étoiles':
|
||
return 4.0;
|
||
case '3+ étoiles':
|
||
return 3.0;
|
||
case '2+ étoiles':
|
||
return 2.0;
|
||
case '1+ étoiles':
|
||
return 1.0;
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
|
||
void _showAddActivityBottomSheet() {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (context) => Padding(
|
||
padding: EdgeInsets.only(
|
||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||
),
|
||
child: AddActivityBottomSheet(trip: widget.trip),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTripActivitiesTab() {
|
||
// Utiliser les données locales au lieu du BLoC
|
||
if (_isLoadingTripActivities) {
|
||
return const Center(child: CircularProgressIndicator());
|
||
}
|
||
|
||
if (_tripActivities.isEmpty) {
|
||
return _buildEmptyState(
|
||
'Aucune activité du voyage',
|
||
'Ajoutez vos premières activités pour ce voyage',
|
||
Icons.add_location,
|
||
);
|
||
}
|
||
|
||
return _buildActivityList(_tripActivities);
|
||
}
|
||
|
||
Widget _buildApprovedActivitiesTab() {
|
||
// Utiliser les données locales au lieu du BLoC
|
||
if (_isLoadingTripActivities) {
|
||
return const Center(child: CircularProgressIndicator());
|
||
}
|
||
|
||
if (_approvedActivities.isEmpty) {
|
||
return _buildEmptyState(
|
||
'Aucune activité approuvée',
|
||
'Les activités avec des votes positifs apparaîtront ici',
|
||
Icons.thumb_up_outlined,
|
||
);
|
||
}
|
||
|
||
return _buildActivityList(_approvedActivities);
|
||
}
|
||
|
||
Widget _buildGoogleSuggestionsTab() {
|
||
return BlocBuilder<ActivityBloc, ActivityState>(
|
||
builder: (context, state) {
|
||
if (state is ActivitySearching) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const CircularProgressIndicator(),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Recherche d\'activités en cours...',
|
||
style: Theme.of(context).textTheme.bodyMedium,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
if (state is ActivitySearchResults) {
|
||
final googleActivities = state.searchResults;
|
||
|
||
// Filtrer les activités déjà présentes dans le voyage
|
||
final filteredActivities = googleActivities.where((googleActivity) {
|
||
return !_tripActivities.any((tripActivity) =>
|
||
tripActivity.name.toLowerCase().trim() == googleActivity.name.toLowerCase().trim());
|
||
}).toList();
|
||
|
||
print('🔍 [Google Search] ${googleActivities.length} résultats trouvés, ${filteredActivities.length} après filtrage');
|
||
|
||
if (filteredActivities.isEmpty && googleActivities.isNotEmpty) {
|
||
return Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
_buildEmptyState(
|
||
'Toutes les activités sont déjà dans votre voyage',
|
||
'Recherchez plus d\'activités pour découvrir de nouvelles suggestions',
|
||
Icons.check_circle,
|
||
),
|
||
const SizedBox(height: 20),
|
||
ElevatedButton.icon(
|
||
onPressed: () => _loadMoreGoogleActivities(),
|
||
icon: const Icon(Icons.add_circle_outline),
|
||
label: const Text('Rechercher plus d\'activités'),
|
||
style: ElevatedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
if (filteredActivities.isEmpty) {
|
||
return Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
_buildEmptyState(
|
||
'Aucun résultat trouvé',
|
||
'Aucune activité trouvée pour cette destination',
|
||
Icons.search_off,
|
||
),
|
||
const SizedBox(height: 20),
|
||
ElevatedButton.icon(
|
||
onPressed: () => _searchGoogleActivities(),
|
||
icon: const Icon(Icons.refresh),
|
||
label: const Text('Rechercher à nouveau'),
|
||
style: ElevatedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
return Column(
|
||
children: [
|
||
// Liste des activités
|
||
Expanded(
|
||
child: RefreshIndicator(
|
||
onRefresh: () async {
|
||
_resetAndSearchGoogleActivities();
|
||
},
|
||
child: ListView.builder(
|
||
padding: const EdgeInsets.all(16),
|
||
itemCount: filteredActivities.length,
|
||
itemBuilder: (context, index) {
|
||
final activity = filteredActivities[index];
|
||
return _buildActivityCard(activity, isGoogleSuggestion: true);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
|
||
// Bouton "Rechercher plus d'activités"
|
||
if (state.isLoading)
|
||
Container(
|
||
padding: const EdgeInsets.all(16),
|
||
child: const Center(
|
||
child: CircularProgressIndicator(),
|
||
),
|
||
)
|
||
else
|
||
Column(
|
||
children: [
|
||
Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.all(16),
|
||
child: ElevatedButton.icon(
|
||
onPressed: () => _loadMoreGoogleActivities(),
|
||
icon: const Icon(Icons.add_circle_outline),
|
||
label: const Text('Rechercher plus d\'activités'),
|
||
style: ElevatedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||
foregroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
// Bouton de debug temporaire
|
||
Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: OutlinedButton.icon(
|
||
onPressed: () {
|
||
print('🧪 [DEBUG] Force auto-reload check - État actuel:');
|
||
print('🧪 [DEBUG] _tripActivities: ${_tripActivities.length}');
|
||
print('🧪 [DEBUG] _autoReloadInProgress: $_autoReloadInProgress');
|
||
print('🧪 [DEBUG] _lastAutoReloadTriggerCount: $_lastAutoReloadTriggerCount');
|
||
_checkAndLoadMoreActivitiesIfNeeded();
|
||
},
|
||
icon: const Icon(Icons.bug_report, size: 16),
|
||
label: const Text('🧪 Test Auto-Reload'),
|
||
style: OutlinedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
// État initial - aucune recherche effectuée
|
||
return Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
_buildEmptyState(
|
||
'Découvrir des activités',
|
||
'Trouvez des restaurants, musées et attractions près de ${widget.trip.location}',
|
||
Icons.explore,
|
||
),
|
||
const SizedBox(height: 20),
|
||
ElevatedButton.icon(
|
||
onPressed: () => _searchGoogleActivities(),
|
||
icon: const Icon(Icons.search),
|
||
label: const Text('Rechercher des activités (6 résultats)'),
|
||
style: ElevatedButton.styleFrom(
|
||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||
foregroundColor: Colors.white,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Recherche powered by Google Places',
|
||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildEmptyState(String title, String subtitle, IconData icon) {
|
||
final theme = Theme.of(context);
|
||
return Center(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(32),
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(
|
||
icon,
|
||
size: 64,
|
||
color: theme.colorScheme.outline,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
title,
|
||
style: theme.textTheme.titleLarge?.copyWith(
|
||
color: theme.colorScheme.onSurface,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
subtitle,
|
||
style: theme.textTheme.bodyMedium?.copyWith(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildActivityList(List<Activity> activities) {
|
||
return RefreshIndicator(
|
||
onRefresh: () async {
|
||
// Rafraîchir selon l'onglet actuel
|
||
if (_tabController.index == 2) {
|
||
// Onglet Google - relancer la recherche
|
||
_searchGoogleActivities();
|
||
} else {
|
||
// Onglets activités du voyage - recharger depuis la base de données
|
||
_loadActivities();
|
||
}
|
||
},
|
||
child: activities.isEmpty
|
||
? ListView(
|
||
children: [
|
||
SizedBox(
|
||
height: MediaQuery.of(context).size.height * 0.6,
|
||
child: _buildEmptyState(
|
||
'Aucune activité',
|
||
'Tirez vers le bas pour actualiser',
|
||
Icons.refresh,
|
||
),
|
||
),
|
||
],
|
||
)
|
||
: ListView.builder(
|
||
padding: const EdgeInsets.all(16),
|
||
itemCount: activities.length,
|
||
itemBuilder: (context, index) {
|
||
final activity = activities[index];
|
||
return _buildActivityCard(activity);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildActivityCard(Activity activity, {bool isGoogleSuggestion = false}) {
|
||
final theme = Theme.of(context);
|
||
|
||
// Vérifier si l'activité existe déjà dans le voyage (pour les suggestions Google)
|
||
final bool activityAlreadyExists = isGoogleSuggestion &&
|
||
_tripActivities.any((tripActivity) =>
|
||
tripActivity.name.toLowerCase().trim() == activity.name.toLowerCase().trim());
|
||
|
||
return Card(
|
||
margin: const EdgeInsets.only(bottom: 12),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Image de l'activité
|
||
if (activity.imageUrl != null && activity.imageUrl!.isNotEmpty)
|
||
ClipRRect(
|
||
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||
child: Container(
|
||
height: 200,
|
||
width: double.infinity,
|
||
child: Image.network(
|
||
activity.imageUrl!,
|
||
fit: BoxFit.cover,
|
||
errorBuilder: (context, error, stackTrace) {
|
||
return Container(
|
||
height: 200,
|
||
color: theme.colorScheme.surfaceContainerHighest,
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(
|
||
Icons.image_not_supported,
|
||
size: 48,
|
||
color: theme.colorScheme.onSurfaceVariant,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'Image non disponible',
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: theme.colorScheme.onSurfaceVariant,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
loadingBuilder: (context, child, loadingProgress) {
|
||
if (loadingProgress == null) return child;
|
||
return Container(
|
||
height: 200,
|
||
color: theme.colorScheme.surfaceContainerHighest,
|
||
child: Center(
|
||
child: CircularProgressIndicator(
|
||
value: loadingProgress.expectedTotalBytes != null
|
||
? loadingProgress.cumulativeBytesLoaded /
|
||
loadingProgress.expectedTotalBytes!
|
||
: null,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
|
||
// Contenu de la carte
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
// Icône de catégorie
|
||
Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: theme.colorScheme.primary.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Icon(
|
||
_getCategoryIcon(activity.category),
|
||
color: theme.colorScheme.primary,
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
// Nom et catégorie
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
activity.name,
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
Text(
|
||
activity.category,
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: theme.colorScheme.primary,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
// Note
|
||
if (activity.rating != null)
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: Colors.amber.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.star, color: Colors.amber, size: 16),
|
||
const SizedBox(width: 4),
|
||
Text(
|
||
activity.rating!.toStringAsFixed(1),
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (activity.description.isNotEmpty) ...[
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
activity.description,
|
||
style: theme.textTheme.bodyMedium?.copyWith(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.8),
|
||
),
|
||
maxLines: 2,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
],
|
||
if (activity.address != null) ...[
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
Icon(
|
||
Icons.location_on,
|
||
size: 16,
|
||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||
),
|
||
const SizedBox(width: 4),
|
||
Expanded(
|
||
child: Text(
|
||
activity.address!,
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
const SizedBox(height: 12),
|
||
// Boutons d'action et votes (différents selon le contexte)
|
||
if (isGoogleSuggestion) ...[
|
||
// Pour les suggestions Google : bouton d'ajout ou indication si déjà ajoutée
|
||
Row(
|
||
children: [
|
||
if (activityAlreadyExists) ...[
|
||
// Activité déjà dans le voyage
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.orange.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: Colors.orange.withOpacity(0.3)),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(
|
||
Icons.check_circle,
|
||
size: 16,
|
||
color: Colors.orange.shade700,
|
||
),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
'Déjà dans le voyage',
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: Colors.orange.shade700,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
] else ...[
|
||
// Bouton pour ajouter l'activité
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
onPressed: () => _addGoogleActivityToTrip(activity),
|
||
icon: const Icon(Icons.add, size: 18),
|
||
label: const Text('Ajouter au voyage'),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: theme.colorScheme.primary,
|
||
foregroundColor: Colors.white,
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
] else ...[
|
||
// Pour les activités du voyage : système de votes
|
||
Row(
|
||
children: [
|
||
// Votes positifs (pouces verts)
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: Colors.green.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(
|
||
'${activity.positiveVotes}',
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: Colors.green.shade700,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
Icon(
|
||
Icons.thumb_up,
|
||
size: 16,
|
||
color: Colors.green.shade700,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
// Votes négatifs (pouces rouges)
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: Colors.red.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(
|
||
'${activity.negativeVotes}',
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: Colors.red.shade700,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
Icon(
|
||
Icons.thumb_down,
|
||
size: 16,
|
||
color: Colors.red.shade700,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Spacer(),
|
||
// Bouton J'aime/J'aime pas
|
||
IconButton(
|
||
onPressed: () => _voteForActivity(activity.id, 1),
|
||
icon: const Icon(Icons.thumb_up),
|
||
iconSize: 20,
|
||
),
|
||
IconButton(
|
||
onPressed: () => _voteForActivity(activity.id, -1),
|
||
icon: const Icon(Icons.thumb_down),
|
||
iconSize: 20,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
IconData _getCategoryIcon(String category) {
|
||
switch (category.toLowerCase()) {
|
||
case 'museum':
|
||
case 'musée':
|
||
return Icons.museum;
|
||
case 'restaurant':
|
||
return Icons.restaurant;
|
||
case 'attraction':
|
||
return Icons.place;
|
||
case 'divertissement':
|
||
case 'entertainment':
|
||
return Icons.sports_esports;
|
||
case 'shopping':
|
||
return Icons.shopping_bag;
|
||
case 'nature':
|
||
return Icons.park;
|
||
case 'culture':
|
||
return Icons.account_balance;
|
||
case 'vie nocturne':
|
||
case 'nightlife':
|
||
return Icons.nightlife;
|
||
case 'sports':
|
||
return Icons.sports;
|
||
case 'détente':
|
||
case 'relaxation':
|
||
return Icons.spa;
|
||
default:
|
||
return Icons.place;
|
||
}
|
||
}
|
||
|
||
void _voteForActivity(String activityId, int vote) {
|
||
print('🗳️ Vote pour activité $activityId: $vote');
|
||
|
||
// TODO: Récupérer l'ID utilisateur actuel
|
||
// Pour l'instant, on utilise un ID temporaire
|
||
final userId = 'current_user_id';
|
||
|
||
context.read<ActivityBloc>().add(VoteForActivity(
|
||
activityId: activityId,
|
||
userId: userId,
|
||
vote: vote,
|
||
));
|
||
|
||
// Afficher un feedback à l'utilisateur
|
||
final message = vote == 1 ? 'Vote positif ajouté !' : 'Vote négatif ajouté !';
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(message),
|
||
duration: const Duration(seconds: 1),
|
||
backgroundColor: vote == 1 ? Colors.green : Colors.orange,
|
||
),
|
||
);
|
||
}
|
||
|
||
void _addGoogleActivityToTrip(Activity activity) {
|
||
print('➕ [Add Activity] Adding ${activity.name} to trip');
|
||
|
||
// Créer une nouvelle activité avec l'ID du voyage
|
||
final newActivity = activity.copyWith(
|
||
tripId: widget.trip.id,
|
||
// Générer un nouvel ID unique pour cette activité dans le voyage
|
||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||
);
|
||
|
||
context.read<ActivityBloc>().add(AddActivity(newActivity));
|
||
|
||
// Afficher un feedback à l'utilisateur
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('${activity.name} ajoutée au voyage !'),
|
||
duration: const Duration(seconds: 2),
|
||
backgroundColor: Colors.green,
|
||
action: SnackBarAction(
|
||
label: 'Voir',
|
||
textColor: Colors.white,
|
||
onPressed: () {
|
||
// Revenir à l'onglet des activités du voyage
|
||
_tabController.animateTo(0);
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _checkAndLoadMoreActivitiesIfNeeded() {
|
||
// Protection contre les rechargements en boucle
|
||
if (_autoReloadInProgress) {
|
||
print('⏸️ [Auto-reload] Auto-reload déjà en cours, skip');
|
||
return;
|
||
}
|
||
|
||
final currentState = context.read<ActivityBloc>().state;
|
||
if (currentState is ActivitySearchResults) {
|
||
final googleActivities = currentState.searchResults;
|
||
|
||
print('🔍 [Auto-reload] Activités du voyage en mémoire: ${_tripActivities.length}');
|
||
print('🔍 [Auto-reload] Activités Google total: ${googleActivities.length}');
|
||
|
||
// Filtrer les activités déjà présentes dans le voyage
|
||
final filteredActivities = googleActivities.where((googleActivity) {
|
||
final isDuplicate = _tripActivities.any((tripActivity) =>
|
||
tripActivity.name.toLowerCase().trim() == googleActivity.name.toLowerCase().trim());
|
||
if (isDuplicate) {
|
||
print('🔍 [Auto-reload] Activité filtrée: ${googleActivity.name}');
|
||
}
|
||
return !isDuplicate;
|
||
}).toList();
|
||
|
||
print('🔍 [Auto-reload] ${filteredActivities.length} activités visibles après filtrage sur ${googleActivities.length} total');
|
||
|
||
// Protection: ne pas redéclencher pour le même nombre d'activités Google
|
||
if (googleActivities.length == _lastAutoReloadTriggerCount) {
|
||
print('🔒 [Auto-reload] Même nombre qu\'avant (${googleActivities.length}), skip pour éviter la boucle');
|
||
return;
|
||
}
|
||
|
||
// Si on a moins de 4 activités visibles ET qu'on n'a pas déjà beaucoup d'activités Google
|
||
if (filteredActivities.length < 4 && googleActivities.length < 20) {
|
||
_autoReloadInProgress = true;
|
||
_lastAutoReloadTriggerCount = googleActivities.length;
|
||
|
||
// Calculer combien d'activités on doit demander
|
||
final activitiesNeeded = 6 - filteredActivities.length; // Manque pour arriver à 6
|
||
final newTotalToRequest = googleActivities.length + activitiesNeeded + 6; // Activités actuelles + ce qui manque + buffer de 6
|
||
|
||
print('🔄 [Auto-reload] DÉCLENCHEMENT: Besoin de ${activitiesNeeded} activités supplémentaires');
|
||
print('📊 [Auto-reload] Demande totale: ${newTotalToRequest} activités (actuellement: ${googleActivities.length})');
|
||
|
||
// Mettre à jour le compteur et recharger avec le nouveau total
|
||
_totalGoogleActivitiesRequested = newTotalToRequest;
|
||
_loadMoreGoogleActivitiesWithTotal(newTotalToRequest);
|
||
|
||
// Libérer le verrou après un délai
|
||
Future.delayed(const Duration(seconds: 3), () {
|
||
_autoReloadInProgress = false;
|
||
print('🔓 [Auto-reload] Verrou libéré');
|
||
});
|
||
} else if (filteredActivities.length >= 4) {
|
||
print('✅ [Auto-reload] Suffisamment d\'activités visibles (${filteredActivities.length} >= 4)');
|
||
} else {
|
||
print('🚫 [Auto-reload] Trop d\'activités Google déjà chargées (${googleActivities.length} >= 20), arrêt auto-reload');
|
||
}
|
||
} else {
|
||
print('⚠️ [Auto-reload] État pas prêt pour auto-chargement: ${currentState.runtimeType}');
|
||
}
|
||
}
|
||
|
||
void _searchGoogleActivities() {
|
||
print('🔍 [Google Search] Initializing first search with 6 results');
|
||
_totalGoogleActivitiesRequested = 6; // Reset du compteur
|
||
_autoReloadInProgress = false; // Reset des protections
|
||
_lastAutoReloadTriggerCount = 0;
|
||
|
||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||
if (widget.trip.hasCoordinates) {
|
||
print('🌍 [Google Search] Using pre-geocoded coordinates: ${widget.trip.latitude}, ${widget.trip.longitude}');
|
||
context.read<ActivityBloc>().add(SearchActivitiesWithCoordinates(
|
||
tripId: widget.trip.id!,
|
||
latitude: widget.trip.latitude!,
|
||
longitude: widget.trip.longitude!,
|
||
category: null, // Rechercher dans toutes les catégories
|
||
maxResults: 6, // Charger 6 résultats à la fois
|
||
reset: true, // Nouveau flag pour reset
|
||
));
|
||
} else {
|
||
print('⚠️ [Google Search] No coordinates available, falling back to destination geocoding');
|
||
context.read<ActivityBloc>().add(SearchActivities(
|
||
tripId: widget.trip.id!,
|
||
destination: widget.trip.location,
|
||
category: null, // Rechercher dans toutes les catégories
|
||
maxResults: 6, // Charger 6 résultats à la fois
|
||
reset: true, // Nouveau flag pour reset
|
||
));
|
||
}
|
||
_googleSearchPerformed = true;
|
||
}
|
||
|
||
void _resetAndSearchGoogleActivities() {
|
||
print('🔄 [Google Search] Resetting and starting fresh search');
|
||
_totalGoogleActivitiesRequested = 6; // Reset du compteur
|
||
_autoReloadInProgress = false; // Reset des protections
|
||
_lastAutoReloadTriggerCount = 0;
|
||
|
||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||
if (widget.trip.hasCoordinates) {
|
||
print('🌍 [Google Search] Using pre-geocoded coordinates: ${widget.trip.latitude}, ${widget.trip.longitude}');
|
||
context.read<ActivityBloc>().add(SearchActivitiesWithCoordinates(
|
||
tripId: widget.trip.id!,
|
||
latitude: widget.trip.latitude!,
|
||
longitude: widget.trip.longitude!,
|
||
category: null,
|
||
maxResults: 6,
|
||
reset: true,
|
||
));
|
||
} else {
|
||
print('⚠️ [Google Search] No coordinates available, falling back to destination geocoding');
|
||
context.read<ActivityBloc>().add(SearchActivities(
|
||
tripId: widget.trip.id!,
|
||
destination: widget.trip.location,
|
||
category: null,
|
||
maxResults: 6,
|
||
reset: true,
|
||
));
|
||
}
|
||
_googleSearchPerformed = true;
|
||
}
|
||
|
||
void _loadMoreGoogleActivities() {
|
||
print('📄 [Google Search] Loading more activities (next 6 results)');
|
||
final currentState = context.read<ActivityBloc>().state;
|
||
|
||
if (currentState is ActivitySearchResults) {
|
||
final currentCount = currentState.searchResults.length;
|
||
final newTotal = currentCount + 6;
|
||
print('📊 [Google Search] Current results count: $currentCount, requesting total: $newTotal');
|
||
|
||
_totalGoogleActivitiesRequested = newTotal;
|
||
|
||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||
if (widget.trip.hasCoordinates) {
|
||
print('🌍 [Google Search] Using pre-geocoded coordinates for more results');
|
||
context.read<ActivityBloc>().add(SearchActivitiesWithCoordinates(
|
||
tripId: widget.trip.id!,
|
||
latitude: widget.trip.latitude!,
|
||
longitude: widget.trip.longitude!,
|
||
category: null,
|
||
maxResults: newTotal, // Demander le total cumulé
|
||
reset: true, // Reset pour avoir tous les résultats d'un coup
|
||
));
|
||
} else {
|
||
print('⚠️ [Google Search] No coordinates available, falling back to destination geocoding');
|
||
context.read<ActivityBloc>().add(SearchActivities(
|
||
tripId: widget.trip.id!,
|
||
destination: widget.trip.location,
|
||
category: null,
|
||
maxResults: newTotal, // Demander le total cumulé
|
||
reset: true, // Reset pour avoir tous les résultats d'un coup
|
||
));
|
||
}
|
||
}
|
||
}
|
||
|
||
void _loadMoreGoogleActivitiesWithTotal(int totalToRequest) {
|
||
print('📈 [Google Search] Loading activities with specific total: $totalToRequest');
|
||
|
||
// Au lieu de reset, on utilise l'offset et append pour forcer plus de résultats
|
||
final currentState = context.read<ActivityBloc>().state;
|
||
if (currentState is ActivitySearchResults) {
|
||
final currentCount = currentState.searchResults.length;
|
||
final additionalNeeded = totalToRequest - currentCount;
|
||
|
||
print('📊 [Google Search] Current: $currentCount, Total demandé: $totalToRequest, Additional: $additionalNeeded');
|
||
|
||
if (additionalNeeded > 0) {
|
||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||
if (widget.trip.hasCoordinates) {
|
||
print('🌍 [Google Search] Using pre-geocoded coordinates for additional results');
|
||
context.read<ActivityBloc>().add(SearchActivitiesWithCoordinates(
|
||
tripId: widget.trip.id!,
|
||
latitude: widget.trip.latitude!,
|
||
longitude: widget.trip.longitude!,
|
||
category: null,
|
||
maxResults: additionalNeeded,
|
||
offset: currentCount,
|
||
appendToExisting: true, // Ajouter aux résultats existants
|
||
));
|
||
} else {
|
||
print('⚠️ [Google Search] No coordinates available, falling back to destination geocoding');
|
||
context.read<ActivityBloc>().add(SearchActivities(
|
||
tripId: widget.trip.id!,
|
||
destination: widget.trip.location,
|
||
category: null,
|
||
maxResults: additionalNeeded,
|
||
offset: currentCount,
|
||
appendToExisting: true, // Ajouter aux résultats existants
|
||
));
|
||
}
|
||
} else {
|
||
print('⚠️ [Google Search] Pas besoin de charger plus (déjà suffisant)');
|
||
}
|
||
} else {
|
||
// Si pas de résultats existants, faire une recherche complète
|
||
if (widget.trip.hasCoordinates) {
|
||
print('🌍 [Google Search] Using pre-geocoded coordinates for fresh search');
|
||
context.read<ActivityBloc>().add(SearchActivitiesWithCoordinates(
|
||
tripId: widget.trip.id!,
|
||
latitude: widget.trip.latitude!,
|
||
longitude: widget.trip.longitude!,
|
||
category: null,
|
||
maxResults: totalToRequest,
|
||
reset: true,
|
||
));
|
||
} else {
|
||
print('⚠️ [Google Search] No coordinates available, falling back to destination geocoding');
|
||
context.read<ActivityBloc>().add(SearchActivities(
|
||
tripId: widget.trip.id!,
|
||
destination: widget.trip.location,
|
||
category: null,
|
||
maxResults: totalToRequest,
|
||
reset: true,
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|