fAdd phone number support to user authentication events and methods
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
/// - [AuthSignOutRequested]: Processes user sign-out
|
/// - [AuthSignOutRequested]: Processes user sign-out
|
||||||
/// - [AuthPasswordResetRequested]: Processes password reset requests
|
/// - [AuthPasswordResetRequested]: Processes password reset requests
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../repositories/auth_repository.dart';
|
import '../../repositories/auth_repository.dart';
|
||||||
import 'auth_event.dart';
|
import 'auth_event.dart';
|
||||||
@@ -35,13 +36,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
/// The bloc starts in the [AuthInitial] state and registers event handlers
|
/// The bloc starts in the [AuthInitial] state and registers event handlers
|
||||||
/// for all supported authentication events.
|
/// for all supported authentication events.
|
||||||
AuthBloc({required AuthRepository authRepository})
|
AuthBloc({required AuthRepository authRepository})
|
||||||
: _authRepository = authRepository,
|
: _authRepository = authRepository,
|
||||||
super(AuthInitial()) {
|
super(AuthInitial()) {
|
||||||
on<AuthCheckRequested>(_onAuthCheckRequested);
|
on<AuthCheckRequested>(_onAuthCheckRequested);
|
||||||
on<AuthSignInRequested>(_onSignInRequested);
|
on<AuthSignInRequested>(_onSignInRequested);
|
||||||
on<AuthSignUpRequested>(_onSignUpRequested);
|
on<AuthSignUpRequested>(_onSignUpRequested);
|
||||||
on<AuthGoogleSignInRequested>(_onGoogleSignInRequested);
|
on<AuthGoogleSignInRequested>(_onGoogleSignInRequested);
|
||||||
|
on<AuthGoogleSignUpRequested>(_onGoogleSignUpRequested);
|
||||||
on<AuthAppleSignInRequested>(_onAppleSignInRequested);
|
on<AuthAppleSignInRequested>(_onAppleSignInRequested);
|
||||||
|
on<AuthAppleSignUpRequested>(_onAppleSignUpRequested);
|
||||||
on<AuthSignOutRequested>(_onSignOutRequested);
|
on<AuthSignOutRequested>(_onSignOutRequested);
|
||||||
on<AuthPasswordResetRequested>(_onPasswordResetRequested);
|
on<AuthPasswordResetRequested>(_onPasswordResetRequested);
|
||||||
}
|
}
|
||||||
@@ -61,7 +64,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
// Fetch user data from Firestore
|
// Fetch user data from Firestore
|
||||||
final user = await _authRepository.getUserFromFirestore(currentUser.uid);
|
final user = await _authRepository.getUserFromFirestore(
|
||||||
|
currentUser.uid,
|
||||||
|
);
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
@@ -118,12 +123,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
password: event.password,
|
password: event.password,
|
||||||
nom: event.nom,
|
nom: event.nom,
|
||||||
prenom: event.prenom,
|
prenom: event.prenom,
|
||||||
|
phoneNumber: event.phoneNumber,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Registration failed'));
|
emit(const AuthError(message: 'Failed to create account'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(AuthError(message: e.toString()));
|
emit(AuthError(message: e.toString()));
|
||||||
@@ -146,7 +152,50 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Google sign-in cancelled'));
|
emit(
|
||||||
|
const AuthError(
|
||||||
|
message:
|
||||||
|
'Utilisateur n\'est pas encore inscrit avec Google, veuillez créer un compte avec Google',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onGoogleSignUpRequested(
|
||||||
|
AuthGoogleSignUpRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
// This method can be implemented if needed for Google sign-up specific logic.
|
||||||
|
emit(AuthLoading());
|
||||||
|
try {
|
||||||
|
final user = await _authRepository.signUpWithGoogle(event.phoneNumber);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(const AuthError(message: 'Failed to create account with Google'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onAppleSignUpRequested(
|
||||||
|
AuthAppleSignUpRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
// This method can be implemented if needed for Apple sign-up specific logic.
|
||||||
|
emit(AuthLoading());
|
||||||
|
try {
|
||||||
|
final user = await _authRepository.signUpWithApple(event.phoneNumber);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(const AuthError(message: 'Failed to create account with Apple'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(AuthError(message: e.toString()));
|
emit(AuthError(message: e.toString()));
|
||||||
@@ -169,7 +218,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Apple sign-in cancelled'));
|
emit(
|
||||||
|
const AuthError(
|
||||||
|
message:
|
||||||
|
'Utilisateur n\'est pas encore inscrit avec Apple, veuillez créer un compte avec Apple',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(AuthError(message: e.toString()));
|
emit(AuthError(message: e.toString()));
|
||||||
|
|||||||
@@ -32,10 +32,7 @@ class AuthSignInRequested extends AuthEvent {
|
|||||||
/// Creates a new [AuthSignInRequested] event.
|
/// Creates a new [AuthSignInRequested] event.
|
||||||
///
|
///
|
||||||
/// Both [email] and [password] are required parameters.
|
/// Both [email] and [password] are required parameters.
|
||||||
const AuthSignInRequested({
|
const AuthSignInRequested({required this.email, required this.password});
|
||||||
required this.email,
|
|
||||||
required this.password,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [email, password];
|
List<Object?> get props => [email, password];
|
||||||
@@ -58,6 +55,9 @@ class AuthSignUpRequested extends AuthEvent {
|
|||||||
/// The user's first name.
|
/// The user's first name.
|
||||||
final String prenom;
|
final String prenom;
|
||||||
|
|
||||||
|
/// The user's phone number.
|
||||||
|
final String phoneNumber;
|
||||||
|
|
||||||
/// Creates a new [AuthSignUpRequested] event.
|
/// Creates a new [AuthSignUpRequested] event.
|
||||||
///
|
///
|
||||||
/// All parameters are required for user registration.
|
/// All parameters are required for user registration.
|
||||||
@@ -66,6 +66,7 @@ class AuthSignUpRequested extends AuthEvent {
|
|||||||
required this.password,
|
required this.password,
|
||||||
required this.nom,
|
required this.nom,
|
||||||
required this.prenom,
|
required this.prenom,
|
||||||
|
required this.phoneNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -77,11 +78,37 @@ class AuthSignUpRequested extends AuthEvent {
|
|||||||
/// This event triggers the Google sign-in flow when dispatched to the [AuthBloc].
|
/// This event triggers the Google sign-in flow when dispatched to the [AuthBloc].
|
||||||
class AuthGoogleSignInRequested extends AuthEvent {}
|
class AuthGoogleSignInRequested extends AuthEvent {}
|
||||||
|
|
||||||
|
class AuthGoogleSignUpRequested extends AuthEvent {
|
||||||
|
/// The user's phone number.
|
||||||
|
final String phoneNumber;
|
||||||
|
|
||||||
|
/// Creates a new [AuthGoogleSignUpRequested] event.
|
||||||
|
///
|
||||||
|
/// The [phoneNumber] parameter is required.
|
||||||
|
const AuthGoogleSignUpRequested({required this.phoneNumber});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [phoneNumber];
|
||||||
|
}
|
||||||
|
|
||||||
/// Event to request user sign-in using Apple authentication.
|
/// Event to request user sign-in using Apple authentication.
|
||||||
///
|
///
|
||||||
/// This event triggers the Apple sign-in flow when dispatched to the [AuthBloc].
|
/// This event triggers the Apple sign-in flow when dispatched to the [AuthBloc].
|
||||||
class AuthAppleSignInRequested extends AuthEvent {}
|
class AuthAppleSignInRequested extends AuthEvent {}
|
||||||
|
|
||||||
|
class AuthAppleSignUpRequested extends AuthEvent {
|
||||||
|
/// The user's phone number.
|
||||||
|
final String phoneNumber;
|
||||||
|
|
||||||
|
/// Creates a new [AuthAppleSignUpRequested] event.
|
||||||
|
///
|
||||||
|
/// The [phoneNumber] parameter is required.
|
||||||
|
const AuthAppleSignUpRequested({required this.phoneNumber});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [phoneNumber];
|
||||||
|
}
|
||||||
|
|
||||||
/// Event to request user sign-out.
|
/// Event to request user sign-out.
|
||||||
///
|
///
|
||||||
/// This event triggers the sign-out process and clears the user session
|
/// This event triggers the sign-out process and clears the user session
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state is ActivityLoaded) {
|
if (state is ActivityLoaded) {
|
||||||
print('✅ Activités chargées: ${state.activities.length}');
|
|
||||||
// Stocker les activités localement
|
// Stocker les activités localement
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -137,28 +136,18 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
_isLoadingTripActivities = false;
|
_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
|
// Vérifier si on a besoin de charger plus d'activités dans les suggestions
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
print(
|
|
||||||
'🚀 [ActivityLoaded] Déclenchement de la vérification auto-reload',
|
|
||||||
);
|
|
||||||
_checkAndLoadMoreActivitiesIfNeeded();
|
_checkAndLoadMoreActivitiesIfNeeded();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is ActivitySearchResults) {
|
if (state is ActivitySearchResults) {
|
||||||
print('🔍 Résultats Google: ${state.searchResults.length}');
|
|
||||||
// Déclencher l'auto-reload uniquement pour la recherche initiale (6 résultats)
|
// Déclencher l'auto-reload uniquement pour la recherche initiale (6 résultats)
|
||||||
// et pas pour les rechargements automatiques
|
// et pas pour les rechargements automatiques
|
||||||
if (state.searchResults.length <= 6 && !_autoReloadInProgress) {
|
if (state.searchResults.length <= 6 && !_autoReloadInProgress) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
print(
|
|
||||||
'🎯 [ActivitySearchResults] Première recherche avec peu de résultats, vérification auto-reload',
|
|
||||||
);
|
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
_checkAndLoadMoreActivitiesIfNeeded();
|
_checkAndLoadMoreActivitiesIfNeeded();
|
||||||
});
|
});
|
||||||
@@ -167,13 +156,11 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state is ActivityVoteRecorded) {
|
if (state is ActivityVoteRecorded) {
|
||||||
print('<EFBFBD>️ Vote enregistré pour activité: ${state.activityId}');
|
|
||||||
// Recharger les activités du voyage pour mettre à jour les votes
|
// Recharger les activités du voyage pour mettre à jour les votes
|
||||||
_loadActivities();
|
_loadActivities();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is ActivityAdded) {
|
if (state is ActivityAdded) {
|
||||||
print('✅ Activité ajoutée avec succès: ${state.activity.name}');
|
|
||||||
// Recharger automatiquement les activités du voyage
|
// Recharger automatiquement les activités du voyage
|
||||||
_loadActivities();
|
_loadActivities();
|
||||||
}
|
}
|
||||||
@@ -654,10 +641,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
print(
|
|
||||||
'🔍 [Google Search] ${googleActivities.length} résultats trouvés, ${filteredActivities.length} après filtrage',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filteredActivities.isEmpty && googleActivities.isNotEmpty) {
|
if (filteredActivities.isEmpty && googleActivities.isNotEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -764,18 +747,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print(
|
|
||||||
'🧪 [DEBUG] Force auto-reload check - État actuel:',
|
|
||||||
);
|
|
||||||
print(
|
|
||||||
'🧪 [DEBUG] _tripActivities: ${_tripActivities.length}',
|
|
||||||
);
|
|
||||||
print(
|
|
||||||
'🧪 [DEBUG] _autoReloadInProgress: $_autoReloadInProgress',
|
|
||||||
);
|
|
||||||
print(
|
|
||||||
'🧪 [DEBUG] _lastAutoReloadTriggerCount: $_lastAutoReloadTriggerCount',
|
|
||||||
);
|
|
||||||
_checkAndLoadMoreActivitiesIfNeeded();
|
_checkAndLoadMoreActivitiesIfNeeded();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.bug_report, size: 16),
|
icon: const Icon(Icons.bug_report, size: 16),
|
||||||
@@ -1251,8 +1222,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _voteForActivity(String activityId, int vote) {
|
void _voteForActivity(String activityId, int vote) {
|
||||||
print('🗳️ Vote pour activité $activityId: $vote');
|
|
||||||
|
|
||||||
// TODO: Récupérer l'ID utilisateur actuel
|
// TODO: Récupérer l'ID utilisateur actuel
|
||||||
// Pour l'instant, on utilise un ID temporaire
|
// Pour l'instant, on utilise un ID temporaire
|
||||||
final userId = 'current_user_id';
|
final userId = 'current_user_id';
|
||||||
@@ -1275,8 +1244,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _addGoogleActivityToTrip(Activity activity) {
|
void _addGoogleActivityToTrip(Activity activity) {
|
||||||
print('➕ [Add Activity] Adding ${activity.name} to trip');
|
|
||||||
|
|
||||||
// Créer une nouvelle activité avec l'ID du voyage
|
// Créer une nouvelle activité avec l'ID du voyage
|
||||||
final newActivity = activity.copyWith(
|
final newActivity = activity.copyWith(
|
||||||
tripId: widget.trip.id,
|
tripId: widget.trip.id,
|
||||||
@@ -1307,7 +1274,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
void _checkAndLoadMoreActivitiesIfNeeded() {
|
void _checkAndLoadMoreActivitiesIfNeeded() {
|
||||||
// Protection contre les rechargements en boucle
|
// Protection contre les rechargements en boucle
|
||||||
if (_autoReloadInProgress) {
|
if (_autoReloadInProgress) {
|
||||||
print('⏸️ [Auto-reload] Auto-reload déjà en cours, skip');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,13 +1281,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
if (currentState is ActivitySearchResults) {
|
if (currentState is ActivitySearchResults) {
|
||||||
final googleActivities = currentState.searchResults;
|
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
|
// Filtrer les activités déjà présentes dans le voyage
|
||||||
final filteredActivities = googleActivities.where((googleActivity) {
|
final filteredActivities = googleActivities.where((googleActivity) {
|
||||||
final isDuplicate = _tripActivities.any(
|
final isDuplicate = _tripActivities.any(
|
||||||
@@ -1329,21 +1288,12 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
tripActivity.name.toLowerCase().trim() ==
|
tripActivity.name.toLowerCase().trim() ==
|
||||||
googleActivity.name.toLowerCase().trim(),
|
googleActivity.name.toLowerCase().trim(),
|
||||||
);
|
);
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {}
|
||||||
print('🔍 [Auto-reload] Activité filtrée: ${googleActivity.name}');
|
|
||||||
}
|
|
||||||
return !isDuplicate;
|
return !isDuplicate;
|
||||||
}).toList();
|
}).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
|
// Protection: ne pas redéclencher pour le même nombre d'activités Google
|
||||||
if (googleActivities.length == _lastAutoReloadTriggerCount) {
|
if (googleActivities.length == _lastAutoReloadTriggerCount) {
|
||||||
print(
|
|
||||||
'🔒 [Auto-reload] Même nombre qu\'avant (${googleActivities.length}), skip pour éviter la boucle',
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1360,13 +1310,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
activitiesNeeded +
|
activitiesNeeded +
|
||||||
6; // Activités actuelles + ce qui manque + buffer de 6
|
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
|
// Mettre à jour le compteur et recharger avec le nouveau total
|
||||||
_totalGoogleActivitiesRequested = newTotalToRequest;
|
_totalGoogleActivitiesRequested = newTotalToRequest;
|
||||||
_loadMoreGoogleActivitiesWithTotal(newTotalToRequest);
|
_loadMoreGoogleActivitiesWithTotal(newTotalToRequest);
|
||||||
@@ -1374,35 +1317,19 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
// Libérer le verrou après un délai
|
// Libérer le verrou après un délai
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
_autoReloadInProgress = false;
|
_autoReloadInProgress = false;
|
||||||
print('🔓 [Auto-reload] Verrou libéré');
|
|
||||||
});
|
});
|
||||||
} else if (filteredActivities.length >= 4) {
|
} else if (filteredActivities.length >= 4) {
|
||||||
print(
|
} else {}
|
||||||
'✅ [Auto-reload] Suffisamment d\'activités visibles (${filteredActivities.length} >= 4)',
|
} else {}
|
||||||
);
|
|
||||||
} 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() {
|
void _searchGoogleActivities() {
|
||||||
print('🔍 [Google Search] Initializing first search with 6 results');
|
|
||||||
_totalGoogleActivitiesRequested = 6; // Reset du compteur
|
_totalGoogleActivitiesRequested = 6; // Reset du compteur
|
||||||
_autoReloadInProgress = false; // Reset des protections
|
_autoReloadInProgress = false; // Reset des protections
|
||||||
_lastAutoReloadTriggerCount = 0;
|
_lastAutoReloadTriggerCount = 0;
|
||||||
|
|
||||||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||||||
if (widget.trip.hasCoordinates) {
|
if (widget.trip.hasCoordinates) {
|
||||||
print(
|
|
||||||
'🌍 [Google Search] Using pre-geocoded coordinates: ${widget.trip.latitude}, ${widget.trip.longitude}',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivitiesWithCoordinates(
|
SearchActivitiesWithCoordinates(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1414,9 +1341,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print(
|
|
||||||
'⚠️ [Google Search] No coordinates available, falling back to destination geocoding',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivities(
|
SearchActivities(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1431,16 +1355,12 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _resetAndSearchGoogleActivities() {
|
void _resetAndSearchGoogleActivities() {
|
||||||
print('🔄 [Google Search] Resetting and starting fresh search');
|
|
||||||
_totalGoogleActivitiesRequested = 6; // Reset du compteur
|
_totalGoogleActivitiesRequested = 6; // Reset du compteur
|
||||||
_autoReloadInProgress = false; // Reset des protections
|
_autoReloadInProgress = false; // Reset des protections
|
||||||
_lastAutoReloadTriggerCount = 0;
|
_lastAutoReloadTriggerCount = 0;
|
||||||
|
|
||||||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||||||
if (widget.trip.hasCoordinates) {
|
if (widget.trip.hasCoordinates) {
|
||||||
print(
|
|
||||||
'🌍 [Google Search] Using pre-geocoded coordinates: ${widget.trip.latitude}, ${widget.trip.longitude}',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivitiesWithCoordinates(
|
SearchActivitiesWithCoordinates(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1452,9 +1372,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print(
|
|
||||||
'⚠️ [Google Search] No coordinates available, falling back to destination geocoding',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivities(
|
SearchActivities(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1469,23 +1386,16 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _loadMoreGoogleActivities() {
|
void _loadMoreGoogleActivities() {
|
||||||
print('📄 [Google Search] Loading more activities (next 6 results)');
|
|
||||||
final currentState = context.read<ActivityBloc>().state;
|
final currentState = context.read<ActivityBloc>().state;
|
||||||
|
|
||||||
if (currentState is ActivitySearchResults) {
|
if (currentState is ActivitySearchResults) {
|
||||||
final currentCount = currentState.searchResults.length;
|
final currentCount = currentState.searchResults.length;
|
||||||
final newTotal = currentCount + 6;
|
final newTotal = currentCount + 6;
|
||||||
print(
|
|
||||||
'📊 [Google Search] Current results count: $currentCount, requesting total: $newTotal',
|
|
||||||
);
|
|
||||||
|
|
||||||
_totalGoogleActivitiesRequested = newTotal;
|
_totalGoogleActivitiesRequested = newTotal;
|
||||||
|
|
||||||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||||||
if (widget.trip.hasCoordinates) {
|
if (widget.trip.hasCoordinates) {
|
||||||
print(
|
|
||||||
'🌍 [Google Search] Using pre-geocoded coordinates for more results',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivitiesWithCoordinates(
|
SearchActivitiesWithCoordinates(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1497,9 +1407,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print(
|
|
||||||
'⚠️ [Google Search] No coordinates available, falling back to destination geocoding',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivities(
|
SearchActivities(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1514,26 +1421,15 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _loadMoreGoogleActivitiesWithTotal(int totalToRequest) {
|
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
|
// Au lieu de reset, on utilise l'offset et append pour forcer plus de résultats
|
||||||
final currentState = context.read<ActivityBloc>().state;
|
final currentState = context.read<ActivityBloc>().state;
|
||||||
if (currentState is ActivitySearchResults) {
|
if (currentState is ActivitySearchResults) {
|
||||||
final currentCount = currentState.searchResults.length;
|
final currentCount = currentState.searchResults.length;
|
||||||
final additionalNeeded = totalToRequest - currentCount;
|
final additionalNeeded = totalToRequest - currentCount;
|
||||||
|
|
||||||
print(
|
|
||||||
'📊 [Google Search] Current: $currentCount, Total demandé: $totalToRequest, Additional: $additionalNeeded',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (additionalNeeded > 0) {
|
if (additionalNeeded > 0) {
|
||||||
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
// Utiliser les coordonnées pré-géolocalisées du voyage si disponibles
|
||||||
if (widget.trip.hasCoordinates) {
|
if (widget.trip.hasCoordinates) {
|
||||||
print(
|
|
||||||
'🌍 [Google Search] Using pre-geocoded coordinates for additional results',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivitiesWithCoordinates(
|
SearchActivitiesWithCoordinates(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1546,9 +1442,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print(
|
|
||||||
'⚠️ [Google Search] No coordinates available, falling back to destination geocoding',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivities(
|
SearchActivities(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1560,15 +1453,10 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {}
|
||||||
print('⚠️ [Google Search] Pas besoin de charger plus (déjà suffisant)');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Si pas de résultats existants, faire une recherche complète
|
// Si pas de résultats existants, faire une recherche complète
|
||||||
if (widget.trip.hasCoordinates) {
|
if (widget.trip.hasCoordinates) {
|
||||||
print(
|
|
||||||
'🌍 [Google Search] Using pre-geocoded coordinates for fresh search',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivitiesWithCoordinates(
|
SearchActivitiesWithCoordinates(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
@@ -1580,9 +1468,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print(
|
|
||||||
'⚠️ [Google Search] No coordinates available, falling back to destination geocoding',
|
|
||||||
);
|
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
SearchActivities(
|
SearchActivities(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
|
|||||||
158
lib/components/loading/laoding_content.dart
Normal file
158
lib/components/loading/laoding_content.dart
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LoadingContent extends StatefulWidget {
|
||||||
|
final Future<void> Function()? onBackgroundTask;
|
||||||
|
final String? loadingText;
|
||||||
|
final VoidCallback? onComplete;
|
||||||
|
|
||||||
|
const LoadingContent({
|
||||||
|
Key? key,
|
||||||
|
this.onBackgroundTask,
|
||||||
|
this.loadingText = "Chargement en cours...",
|
||||||
|
this.onComplete,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoadingContent> createState() => _LoadingContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoadingContentState extends State<LoadingContent>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController _rotationController;
|
||||||
|
late AnimationController _pulseController;
|
||||||
|
late Animation<double> _rotationAnimation;
|
||||||
|
late Animation<double> _pulseAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_rotationController = AnimationController(
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
vsync: this,
|
||||||
|
)..repeat();
|
||||||
|
|
||||||
|
_pulseController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 1500),
|
||||||
|
vsync: this,
|
||||||
|
)..repeat(reverse: true);
|
||||||
|
|
||||||
|
_rotationAnimation = Tween<double>(
|
||||||
|
begin: 0,
|
||||||
|
end: 1,
|
||||||
|
).animate(_rotationController);
|
||||||
|
|
||||||
|
_pulseAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(
|
||||||
|
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
|
||||||
|
);
|
||||||
|
|
||||||
|
_executeBackgroundTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _executeBackgroundTask() async {
|
||||||
|
if (widget.onBackgroundTask != null) {
|
||||||
|
try {
|
||||||
|
await widget.onBackgroundTask!();
|
||||||
|
if (widget.onComplete != null) {
|
||||||
|
widget.onComplete!();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Gérer les erreurs si nécessaire
|
||||||
|
print('Erreur lors de la tâche en arrière-plan: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_rotationController.dispose();
|
||||||
|
_pulseController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Animation de rotation et pulsation
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: Listenable.merge([
|
||||||
|
_rotationAnimation,
|
||||||
|
_pulseAnimation,
|
||||||
|
]),
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: _pulseAnimation.value,
|
||||||
|
child: RotationTransition(
|
||||||
|
turns: _rotationAnimation,
|
||||||
|
child: Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Colors.blue.shade400,
|
||||||
|
Colors.purple.shade400,
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blue.withOpacity(0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
spreadRadius: 5,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.travel_explore,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
// Texte de chargement avec animation
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _pulseController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: _pulseAnimation.value - 0.3,
|
||||||
|
child: Text(
|
||||||
|
widget.loadingText ?? "Chargement en cours...",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Indicateur de progression linéaire
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
backgroundColor: Colors.grey.shade300,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue.shade400),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
232
lib/components/signup/sign_up_platform_content.dart
Normal file
232
lib/components/signup/sign_up_platform_content.dart
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SignUpPlatformContent extends StatefulWidget {
|
||||||
|
final String platform; // 'google' ou 'apple'
|
||||||
|
final String? email;
|
||||||
|
final String? phoneNumber;
|
||||||
|
final String? name;
|
||||||
|
final String? firstName;
|
||||||
|
|
||||||
|
const SignUpPlatformContent({
|
||||||
|
Key? key,
|
||||||
|
required this.platform,
|
||||||
|
this.email,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.name,
|
||||||
|
this.firstName,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SignUpPlatformContent> createState() => _SignUpPlatformContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignUpPlatformContentState extends State<SignUpPlatformContent> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late TextEditingController _nameController;
|
||||||
|
late TextEditingController _firstNameController;
|
||||||
|
late TextEditingController _emailController;
|
||||||
|
late TextEditingController _phoneController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_nameController = TextEditingController(text: widget.name ?? '');
|
||||||
|
_firstNameController = TextEditingController(text: widget.firstName ?? '');
|
||||||
|
_emailController = TextEditingController(text: widget.email ?? '');
|
||||||
|
_phoneController = TextEditingController(text: widget.phoneNumber ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
_firstNameController.dispose();
|
||||||
|
_emailController.dispose();
|
||||||
|
_phoneController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateField(String? value, String fieldName) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '$fieldName est requis';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submitForm() {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
// Traitement de l'inscription
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Profil complété avec succès!')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPlatformIndicator() {
|
||||||
|
Color platformColor = widget.platform == 'google'
|
||||||
|
? Colors.red
|
||||||
|
: Colors.black;
|
||||||
|
IconData platformIcon = widget.platform == 'google'
|
||||||
|
? Icons.g_mobiledata
|
||||||
|
: Icons.apple;
|
||||||
|
String platformName = widget.platform == 'google' ? 'Google' : 'Apple';
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: widget.platform == 'google'
|
||||||
|
? Colors.red.shade50
|
||||||
|
: Colors.black.withValues(alpha: 0.05),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: platformColor, width: 1),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(platformIcon, color: platformColor, size: 24),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Connecté avec $platformName',
|
||||||
|
style: TextStyle(
|
||||||
|
color: platformColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Indicateur de plateforme
|
||||||
|
_buildPlatformIndicator(),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Titre
|
||||||
|
const Text(
|
||||||
|
'Complétez votre profil',
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
'Vérifiez et complétez vos informations',
|
||||||
|
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Champ Nom
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Nom',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(Icons.person_outline),
|
||||||
|
),
|
||||||
|
validator: (value) => _validateField(value, 'Nom'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Champ Prénom
|
||||||
|
TextFormField(
|
||||||
|
controller: _firstNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Prénom',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(Icons.person_outline),
|
||||||
|
),
|
||||||
|
validator: (value) => _validateField(value, 'Prénom'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Champ Email (non modifiable)
|
||||||
|
TextFormField(
|
||||||
|
controller: _emailController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Email',
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
prefixIcon: const Icon(Icons.email_outlined),
|
||||||
|
suffixIcon: Icon(
|
||||||
|
Icons.lock_outline,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabled: false,
|
||||||
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Email fourni par ${widget.platform == 'google' ? 'Google' : 'Apple'}',
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Champ Téléphone
|
||||||
|
TextFormField(
|
||||||
|
controller: _phoneController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Numéro de téléphone',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(Icons.phone_outlined),
|
||||||
|
hintText: '+33 6 12 34 56 78',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
validator: (value) =>
|
||||||
|
_validateField(value, 'Numéro de téléphone'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Bouton de confirmation
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _submitForm,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Confirmer mon inscription',
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Bouton secondaire
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text(
|
||||||
|
'Modifier la méthode de connexion',
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
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:sign_in_button/sign_in_button.dart';
|
||||||
|
import 'package:travel_mate/components/loading/laoding_content.dart';
|
||||||
|
import 'package:travel_mate/components/signup/sign_up_platform_content.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';
|
||||||
|
import '../services/error_service.dart';
|
||||||
|
|
||||||
class SignUpPage extends StatefulWidget {
|
class SignUpPage extends StatefulWidget {
|
||||||
const SignUpPage({super.key});
|
const SignUpPage({super.key});
|
||||||
@@ -18,10 +22,13 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _confirmPasswordController = TextEditingController();
|
final _confirmPasswordController = TextEditingController();
|
||||||
|
final _phoneNumberController = TextEditingController();
|
||||||
|
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
bool _obscureConfirmPassword = true;
|
bool _obscureConfirmPassword = true;
|
||||||
|
|
||||||
|
final _errorService = ErrorService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_nomController.dispose();
|
_nomController.dispose();
|
||||||
@@ -76,13 +83,14 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.read<AuthBloc>().add(
|
context.read<AuthBloc>().add(
|
||||||
AuthSignUpRequested(
|
AuthSignUpRequested(
|
||||||
email: _emailController.text.trim(),
|
email: _emailController.text.trim(),
|
||||||
password: _passwordController.text,
|
password: _passwordController.text,
|
||||||
nom: _nomController.text.trim(),
|
nom: _nomController.text.trim(),
|
||||||
prenom: _prenomController.text.trim(),
|
prenom: _prenomController.text.trim(),
|
||||||
),
|
phoneNumber: _phoneNumberController.text.trim(),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -107,11 +115,8 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
);
|
);
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
Navigator.pushReplacementNamed(context, '/home');
|
||||||
} else if (state is AuthError) {
|
} else if (state is AuthError) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
_errorService.showError(
|
||||||
SnackBar(
|
message: 'Erreur lors de la création du compte',
|
||||||
content: Text(state.message),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -129,7 +134,10 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
const Text(
|
const Text(
|
||||||
'Créer un compte',
|
'Créer un compte',
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Text(
|
const Text(
|
||||||
@@ -166,6 +174,21 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Champ Numéro de téléphone
|
||||||
|
TextFormField(
|
||||||
|
controller: _phoneNumberController,
|
||||||
|
validator: (value) =>
|
||||||
|
_validateField(value, 'Numéro de téléphone'),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Numéro de téléphone',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.phone),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Champ Email
|
// Champ Email
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
@@ -227,7 +250,8 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_obscureConfirmPassword = !_obscureConfirmPassword;
|
_obscureConfirmPassword =
|
||||||
|
!_obscureConfirmPassword;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -247,8 +271,13 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: isLoading
|
child: isLoading
|
||||||
? const CircularProgressIndicator(color: Colors.white)
|
? const CircularProgressIndicator(
|
||||||
: const Text('S\'inscrire', style: TextStyle(fontSize: 18)),
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'S\'inscrire',
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
@@ -270,6 +299,86 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Divider
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 1,
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
const Text(
|
||||||
|
'Ou inscrivez-vous avec',
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
SignInButton(
|
||||||
|
Buttons.google,
|
||||||
|
onPressed: () async {
|
||||||
|
// Afficher la page de loading
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => LoadingContent(
|
||||||
|
onBackgroundTask: () async {
|
||||||
|
// Effectuer la requête vers Google
|
||||||
|
final platformData = await _fetchGoogleSignInData();
|
||||||
|
return platformData;
|
||||||
|
},
|
||||||
|
onComplete: () {
|
||||||
|
// Fermer le loading et naviguer vers SignUpPlatformContent
|
||||||
|
Navigator.pop(context); // Fermer LoadingContent
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
SignUpPlatformContent(platform: 'google'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
SignInButton(
|
||||||
|
Buttons.apple,
|
||||||
|
onPressed: () async {
|
||||||
|
// Afficher la page de loading
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => LoadingContent(
|
||||||
|
onBackgroundTask: () async {
|
||||||
|
// Effectuer la requête vers Google
|
||||||
|
final platformData = await
|
||||||
|
return platformData;
|
||||||
|
},
|
||||||
|
onComplete: () {
|
||||||
|
// Fermer le loading et naviguer vers SignUpPlatformContent
|
||||||
|
Navigator.pop(context); // Fermer LoadingContent
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
SignUpPlatformContent(platform: 'apple'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class AuthRepository {
|
|||||||
/// [password] - User's password
|
/// [password] - User's password
|
||||||
/// [nom] - User's last name
|
/// [nom] - User's last name
|
||||||
/// [prenom] - User's first name
|
/// [prenom] - User's first name
|
||||||
|
/// [phoneNumber] - User's phone number
|
||||||
///
|
///
|
||||||
/// Returns the created [User] model if successful.
|
/// Returns the created [User] model if successful.
|
||||||
/// Throws an exception if account creation fails.
|
/// Throws an exception if account creation fails.
|
||||||
@@ -81,6 +82,7 @@ class AuthRepository {
|
|||||||
required String password,
|
required String password,
|
||||||
required String nom,
|
required String nom,
|
||||||
required String prenom,
|
required String prenom,
|
||||||
|
required String phoneNumber,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final firebaseUser = await _authService.signUpWithEmailAndPassword(
|
final firebaseUser = await _authService.signUpWithEmailAndPassword(
|
||||||
@@ -94,7 +96,7 @@ class AuthRepository {
|
|||||||
email: email,
|
email: email,
|
||||||
nom: nom,
|
nom: nom,
|
||||||
prenom: prenom,
|
prenom: prenom,
|
||||||
phoneNumber: 'Uknown',
|
phoneNumber: phoneNumber,
|
||||||
platform: 'email',
|
platform: 'email',
|
||||||
profilePictureUrl: '',
|
profilePictureUrl: '',
|
||||||
);
|
);
|
||||||
@@ -114,7 +116,7 @@ class AuthRepository {
|
|||||||
///
|
///
|
||||||
/// Returns the [User] model if successful, null if Google sign-in was cancelled.
|
/// Returns the [User] model if successful, null if Google sign-in was cancelled.
|
||||||
/// Throws an exception if authentication fails.
|
/// Throws an exception if authentication fails.
|
||||||
Future<User?> signInWithGoogle() async {
|
Future<User?> signUpWithGoogle(String phoneNumber) async {
|
||||||
try {
|
try {
|
||||||
final firebaseUser = await _authService.signInWithGoogle();
|
final firebaseUser = await _authService.signInWithGoogle();
|
||||||
|
|
||||||
@@ -132,8 +134,8 @@ class AuthRepository {
|
|||||||
email: firebaseUser.user!.email ?? '',
|
email: firebaseUser.user!.email ?? '',
|
||||||
nom: '',
|
nom: '',
|
||||||
prenom: firebaseUser.user!.displayName ?? 'User',
|
prenom: firebaseUser.user!.displayName ?? 'User',
|
||||||
phoneNumber: firebaseUser.user!.phoneNumber ?? 'Uknown',
|
phoneNumber: phoneNumber,
|
||||||
profilePictureUrl: firebaseUser.user!.photoURL,
|
profilePictureUrl: firebaseUser.user!.photoURL ?? 'Unknown',
|
||||||
platform: 'google',
|
platform: 'google',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -147,6 +149,21 @@ class AuthRepository {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<User?> signInWithGoogle() async {
|
||||||
|
try {
|
||||||
|
final firebaseUser = await _authService.signInWithGoogle();
|
||||||
|
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
|
if (user != null) {
|
||||||
|
return user;
|
||||||
|
} else {
|
||||||
|
throw Exception('Utilisateur non trouvé');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_errorService.showError(message: 'Erreur lors de la connexion Google');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Signs in a user using Apple authentication.
|
/// Signs in a user using Apple authentication.
|
||||||
///
|
///
|
||||||
/// Handles Apple sign-in flow and creates/retrieves user data from Firestore.
|
/// Handles Apple sign-in flow and creates/retrieves user data from Firestore.
|
||||||
@@ -154,7 +171,7 @@ class AuthRepository {
|
|||||||
///
|
///
|
||||||
/// Returns the [User] model if successful, null if Apple sign-in was cancelled.
|
/// Returns the [User] model if successful, null if Apple sign-in was cancelled.
|
||||||
/// Throws an exception if authentication fails.
|
/// Throws an exception if authentication fails.
|
||||||
Future<User?> signInWithApple() async {
|
Future<User?> signUpWithApple(String phoneNumber) async {
|
||||||
try {
|
try {
|
||||||
final firebaseUser = await _authService.signInWithApple();
|
final firebaseUser = await _authService.signInWithApple();
|
||||||
|
|
||||||
@@ -170,8 +187,8 @@ class AuthRepository {
|
|||||||
email: firebaseUser.user!.email ?? '',
|
email: firebaseUser.user!.email ?? '',
|
||||||
nom: '',
|
nom: '',
|
||||||
prenom: firebaseUser.user!.displayName ?? 'User',
|
prenom: firebaseUser.user!.displayName ?? 'User',
|
||||||
phoneNumber: firebaseUser.user!.phoneNumber ?? 'Uknown',
|
phoneNumber: phoneNumber,
|
||||||
profilePictureUrl: firebaseUser.user!.photoURL,
|
profilePictureUrl: firebaseUser.user!.photoURL ?? 'Unknown',
|
||||||
platform: 'apple',
|
platform: 'apple',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -185,6 +202,21 @@ class AuthRepository {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<User?> signInWithApple() async {
|
||||||
|
try {
|
||||||
|
final firebaseUser = await _authService.signInWithApple();
|
||||||
|
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
|
if (user != null) {
|
||||||
|
return user;
|
||||||
|
} else {
|
||||||
|
throw Exception('Utilisateur non trouvé');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_errorService.showError(message: 'Erreur lors de la connexion Apple');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Signs out the current user.
|
/// Signs out the current user.
|
||||||
///
|
///
|
||||||
/// Clears the authentication state and signs out from Firebase Auth.
|
/// Clears the authentication state and signs out from Firebase Auth.
|
||||||
|
|||||||
Reference in New Issue
Block a user