Refactor signup page to use BLoC pattern and implement authentication repository
- Updated signup.dart to replace Provider with BLoC for state management. - Created AuthRepository to handle authentication logic and Firestore user management. - Added TripRepository and UserRepository for trip and user data management. - Implemented methods for user sign-in, sign-up, and data retrieval in repositories. - Enhanced trip management with create, update, delete, and participant management functionalities. - Updated AuthService to include new methods for sign-in and sign-up. - Removed unnecessary print statements from TripService for cleaner code. - Added dependencies for flutter_bloc and equatable in pubspec.yaml. Not tested yet
This commit is contained in:
150
lib/blocs/auth/auth_bloc.dart
Normal file
150
lib/blocs/auth/auth_bloc.dart
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../repositories/auth_repository.dart';
|
||||||
|
import 'auth_event.dart';
|
||||||
|
import 'auth_state.dart';
|
||||||
|
|
||||||
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
|
final AuthRepository _authRepository;
|
||||||
|
|
||||||
|
AuthBloc({required AuthRepository authRepository})
|
||||||
|
: _authRepository = authRepository,
|
||||||
|
super(AuthInitial()) {
|
||||||
|
on<AuthCheckRequested>(_onAuthCheckRequested);
|
||||||
|
on<AuthSignInRequested>(_onSignInRequested);
|
||||||
|
on<AuthSignUpRequested>(_onSignUpRequested);
|
||||||
|
on<AuthGoogleSignInRequested>(_onGoogleSignInRequested);
|
||||||
|
on<AuthAppleSignInRequested>(_onAppleSignInRequested);
|
||||||
|
on<AuthSignOutRequested>(_onSignOutRequested);
|
||||||
|
on<AuthPasswordResetRequested>(_onPasswordResetRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onAuthCheckRequested(
|
||||||
|
AuthCheckRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final currentUser = _authRepository.currentUser;
|
||||||
|
|
||||||
|
if (currentUser != null) {
|
||||||
|
// Récupérer les données utilisateur depuis Firestore
|
||||||
|
final user = await _authRepository.getUserFromFirestore(currentUser.uid);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(AuthUnauthenticated());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit(AuthUnauthenticated());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSignInRequested(
|
||||||
|
AuthSignInRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final user = await _authRepository.signInWithEmailAndPassword(
|
||||||
|
email: event.email,
|
||||||
|
password: event.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(const AuthError(message: 'Email ou mot de passe incorrect'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSignUpRequested(
|
||||||
|
AuthSignUpRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final user = await _authRepository.signUpWithEmailAndPassword(
|
||||||
|
email: event.email,
|
||||||
|
password: event.password,
|
||||||
|
nom: event.nom,
|
||||||
|
prenom: event.prenom,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(const AuthError(message: 'Erreur lors de l\'inscription'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onGoogleSignInRequested(
|
||||||
|
AuthGoogleSignInRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final user = await _authRepository.signInWithGoogle();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(const AuthError(message: 'Connexion Google annulée'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onAppleSignInRequested(
|
||||||
|
AuthAppleSignInRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final user = await _authRepository.signInWithApple();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
emit(AuthAuthenticated(user: user));
|
||||||
|
} else {
|
||||||
|
emit(const AuthError(message: 'Connexion Apple annulée'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSignOutRequested(
|
||||||
|
AuthSignOutRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
await _authRepository.signOut();
|
||||||
|
emit(AuthUnauthenticated());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onPasswordResetRequested(
|
||||||
|
AuthPasswordResetRequested event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await _authRepository.resetPassword(event.email);
|
||||||
|
emit(AuthPasswordResetSent(email: event.email));
|
||||||
|
} catch (e) {
|
||||||
|
emit(AuthError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
lib/blocs/auth/auth_event.dart
Normal file
55
lib/blocs/auth/auth_event.dart
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
abstract class AuthEvent extends Equatable {
|
||||||
|
const AuthEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthCheckRequested extends AuthEvent {}
|
||||||
|
|
||||||
|
class AuthSignInRequested extends AuthEvent {
|
||||||
|
final String email;
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
const AuthSignInRequested({
|
||||||
|
required this.email,
|
||||||
|
required this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [email, password];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthSignUpRequested extends AuthEvent {
|
||||||
|
final String email;
|
||||||
|
final String password;
|
||||||
|
final String nom;
|
||||||
|
final String prenom;
|
||||||
|
|
||||||
|
const AuthSignUpRequested({
|
||||||
|
required this.email,
|
||||||
|
required this.password,
|
||||||
|
required this.nom,
|
||||||
|
required this.prenom,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [email, password, nom, prenom];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthGoogleSignInRequested extends AuthEvent {}
|
||||||
|
|
||||||
|
class AuthAppleSignInRequested extends AuthEvent {}
|
||||||
|
|
||||||
|
class AuthSignOutRequested extends AuthEvent {}
|
||||||
|
|
||||||
|
class AuthPasswordResetRequested extends AuthEvent {
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
const AuthPasswordResetRequested({required this.email});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [email];
|
||||||
|
}
|
||||||
42
lib/blocs/auth/auth_state.dart
Normal file
42
lib/blocs/auth/auth_state.dart
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import '../../data/models/user.dart';
|
||||||
|
|
||||||
|
abstract class AuthState extends Equatable {
|
||||||
|
const AuthState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthInitial extends AuthState {}
|
||||||
|
|
||||||
|
class AuthLoading extends AuthState {}
|
||||||
|
|
||||||
|
class AuthAuthenticated extends AuthState {
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
const AuthAuthenticated({required this.user});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [user];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthUnauthenticated extends AuthState {}
|
||||||
|
|
||||||
|
class AuthError extends AuthState {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const AuthError({required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [message];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthPasswordResetSent extends AuthState {
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
const AuthPasswordResetSent({required this.email});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [email];
|
||||||
|
}
|
||||||
46
lib/blocs/theme/theme_bloc.dart
Normal file
46
lib/blocs/theme/theme_bloc.dart
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'theme_event.dart';
|
||||||
|
import 'theme_state.dart';
|
||||||
|
|
||||||
|
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
|
||||||
|
ThemeBloc() : super(const ThemeState()) {
|
||||||
|
on<ThemeChanged>(_onThemeChanged);
|
||||||
|
on<ThemeLoadRequested>(_onThemeLoadRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onThemeChanged(
|
||||||
|
ThemeChanged event,
|
||||||
|
Emitter<ThemeState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(themeMode: event.themeMode));
|
||||||
|
|
||||||
|
// Sauvegarder la préférence
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('themeMode', event.themeMode.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onThemeLoadRequested(
|
||||||
|
ThemeLoadRequested event,
|
||||||
|
Emitter<ThemeState> emit,
|
||||||
|
) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final themeModeString = prefs.getString('themeMode');
|
||||||
|
|
||||||
|
if (themeModeString != null) {
|
||||||
|
ThemeMode themeMode;
|
||||||
|
switch (themeModeString) {
|
||||||
|
case 'ThemeMode.light':
|
||||||
|
themeMode = ThemeMode.light;
|
||||||
|
break;
|
||||||
|
case 'ThemeMode.dark':
|
||||||
|
themeMode = ThemeMode.dark;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
themeMode = ThemeMode.system;
|
||||||
|
}
|
||||||
|
emit(state.copyWith(themeMode: themeMode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lib/blocs/theme/theme_event.dart
Normal file
20
lib/blocs/theme/theme_event.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
abstract class ThemeEvent extends Equatable {
|
||||||
|
const ThemeEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThemeChanged extends ThemeEvent {
|
||||||
|
final ThemeMode themeMode;
|
||||||
|
|
||||||
|
const ThemeChanged({required this.themeMode});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [themeMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThemeLoadRequested extends ThemeEvent {}
|
||||||
21
lib/blocs/theme/theme_state.dart
Normal file
21
lib/blocs/theme/theme_state.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ThemeState extends Equatable {
|
||||||
|
final ThemeMode themeMode;
|
||||||
|
|
||||||
|
const ThemeState({this.themeMode = ThemeMode.system});
|
||||||
|
|
||||||
|
bool get isDarkMode {
|
||||||
|
return themeMode == ThemeMode.dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeState copyWith({ThemeMode? themeMode}) {
|
||||||
|
return ThemeState(
|
||||||
|
themeMode: themeMode ?? this.themeMode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [themeMode];
|
||||||
|
}
|
||||||
118
lib/blocs/trip/trip_bloc.dart
Normal file
118
lib/blocs/trip/trip_bloc.dart
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../repositories/trip_repository.dart';
|
||||||
|
import 'trip_event.dart';
|
||||||
|
import 'trip_state.dart';
|
||||||
|
import '../../data/models/trip.dart';
|
||||||
|
|
||||||
|
class TripBloc extends Bloc<TripEvent, TripState> {
|
||||||
|
final TripRepository _tripRepository;
|
||||||
|
StreamSubscription? _tripsSubscription;
|
||||||
|
|
||||||
|
TripBloc({required TripRepository tripRepository})
|
||||||
|
: _tripRepository = tripRepository,
|
||||||
|
super(TripInitial()) {
|
||||||
|
on<TripLoadRequested>(_onLoadRequested);
|
||||||
|
on<TripCreateRequested>(_onCreateRequested);
|
||||||
|
on<TripUpdateRequested>(_onUpdateRequested);
|
||||||
|
on<TripDeleteRequested>(_onDeleteRequested);
|
||||||
|
on<TripParticipantAddRequested>(_onParticipantAddRequested);
|
||||||
|
on<TripParticipantRemoveRequested>(_onParticipantRemoveRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadRequested(
|
||||||
|
TripLoadRequested event,
|
||||||
|
Emitter<TripState> emit,
|
||||||
|
) async {
|
||||||
|
emit(TripLoading());
|
||||||
|
|
||||||
|
await _tripsSubscription?.cancel();
|
||||||
|
|
||||||
|
_tripsSubscription = _tripRepository.getUserTrips(event.userId).listen(
|
||||||
|
(trips) => add(const _TripUpdated(trips: [])), // Sera géré par un événement interne
|
||||||
|
onError: (error) => emit(TripError(message: error.toString())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onCreateRequested(
|
||||||
|
TripCreateRequested event,
|
||||||
|
Emitter<TripState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await _tripRepository.createTrip(event.trip);
|
||||||
|
emit(const TripOperationSuccess(message: 'Voyage créé avec succès'));
|
||||||
|
} catch (e) {
|
||||||
|
emit(TripError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onUpdateRequested(
|
||||||
|
TripUpdateRequested event,
|
||||||
|
Emitter<TripState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await _tripRepository.updateTrip(event.trip);
|
||||||
|
emit(const TripOperationSuccess(message: 'Voyage mis à jour'));
|
||||||
|
} catch (e) {
|
||||||
|
emit(TripError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDeleteRequested(
|
||||||
|
TripDeleteRequested event,
|
||||||
|
Emitter<TripState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await _tripRepository.deleteTrip(event.tripId);
|
||||||
|
emit(const TripOperationSuccess(message: 'Voyage supprimé'));
|
||||||
|
} catch (e) {
|
||||||
|
emit(TripError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onParticipantAddRequested(
|
||||||
|
TripParticipantAddRequested event,
|
||||||
|
Emitter<TripState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await _tripRepository.addParticipant(
|
||||||
|
event.tripId,
|
||||||
|
event.participantEmail,
|
||||||
|
);
|
||||||
|
emit(const TripOperationSuccess(message: 'Participant ajouté'));
|
||||||
|
} catch (e) {
|
||||||
|
emit(TripError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onParticipantRemoveRequested(
|
||||||
|
TripParticipantRemoveRequested event,
|
||||||
|
Emitter<TripState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await _tripRepository.removeParticipant(
|
||||||
|
event.tripId,
|
||||||
|
event.participantEmail,
|
||||||
|
);
|
||||||
|
emit(const TripOperationSuccess(message: 'Participant retiré'));
|
||||||
|
} catch (e) {
|
||||||
|
emit(TripError(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_tripsSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Événement interne pour les mises à jour du stream
|
||||||
|
class _TripUpdated extends TripEvent {
|
||||||
|
final List<Trip> trips;
|
||||||
|
|
||||||
|
const _TripUpdated({required this.trips});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [trips];
|
||||||
|
}
|
||||||
71
lib/blocs/trip/trip_event.dart
Normal file
71
lib/blocs/trip/trip_event.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import '../../data/models/trip.dart';
|
||||||
|
|
||||||
|
abstract class TripEvent extends Equatable {
|
||||||
|
const TripEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripLoadRequested extends TripEvent {
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
const TripLoadRequested({required this.userId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripCreateRequested extends TripEvent {
|
||||||
|
final Trip trip;
|
||||||
|
|
||||||
|
const TripCreateRequested({required this.trip});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [trip];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripUpdateRequested extends TripEvent {
|
||||||
|
final Trip trip;
|
||||||
|
|
||||||
|
const TripUpdateRequested({required this.trip});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [trip];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripDeleteRequested extends TripEvent {
|
||||||
|
final String tripId;
|
||||||
|
|
||||||
|
const TripDeleteRequested({required this.tripId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [tripId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripParticipantAddRequested extends TripEvent {
|
||||||
|
final String tripId;
|
||||||
|
final String participantEmail;
|
||||||
|
|
||||||
|
const TripParticipantAddRequested({
|
||||||
|
required this.tripId,
|
||||||
|
required this.participantEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [tripId, participantEmail];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripParticipantRemoveRequested extends TripEvent {
|
||||||
|
final String tripId;
|
||||||
|
final String participantEmail;
|
||||||
|
|
||||||
|
const TripParticipantRemoveRequested({
|
||||||
|
required this.tripId,
|
||||||
|
required this.participantEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [tripId, participantEmail];
|
||||||
|
}
|
||||||
40
lib/blocs/trip/trip_state.dart
Normal file
40
lib/blocs/trip/trip_state.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import '../../data/models/trip.dart';
|
||||||
|
|
||||||
|
abstract class TripState extends Equatable {
|
||||||
|
const TripState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripInitial extends TripState {}
|
||||||
|
|
||||||
|
class TripLoading extends TripState {}
|
||||||
|
|
||||||
|
class TripLoaded extends TripState {
|
||||||
|
final List<Trip> trips;
|
||||||
|
|
||||||
|
const TripLoaded({required this.trips});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [trips];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripOperationSuccess extends TripState {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const TripOperationSuccess({required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [message];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TripError extends TripState {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const TripError({required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [message];
|
||||||
|
}
|
||||||
0
lib/blocs/user/user_bloc.dart
Normal file
0
lib/blocs/user/user_bloc.dart
Normal file
0
lib/blocs/user/user_event.dart
Normal file
0
lib/blocs/user/user_event.dart
Normal file
0
lib/blocs/user/user_state.dart
Normal file
0
lib/blocs/user/user_state.dart
Normal file
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:travel_mate/models/trip.dart';
|
import 'package:travel_mate/data/models/trip.dart';
|
||||||
import 'package:travel_mate/providers/user_provider.dart';
|
import 'package:travel_mate/providers/user_provider.dart';
|
||||||
import 'package:travel_mate/services/trip_service.dart';
|
import 'package:travel_mate/services/trip_service.dart';
|
||||||
import 'package:travel_mate/services/group_service.dart';
|
import 'package:travel_mate/services/group_service.dart';
|
||||||
import 'package:travel_mate/models/group.dart';
|
import 'package:travel_mate/data/models/group.dart';
|
||||||
|
|
||||||
|
|
||||||
class CreateTripContent extends StatefulWidget {
|
class CreateTripContent extends StatefulWidget {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:travel_mate/components/home/create_trip_content.dart';
|
import 'package:travel_mate/components/home/create_trip_content.dart';
|
||||||
import '../../providers/user_provider.dart';
|
import '../../providers/user_provider.dart';
|
||||||
import '../../services/trip_service.dart';
|
import '../../services/trip_service.dart';
|
||||||
import '../../models/trip.dart';
|
import '../../data/models/trip.dart';
|
||||||
import '../home/show_trip_details_content.dart';
|
import '../home/show_trip_details_content.dart';
|
||||||
|
|
||||||
class HomeContent extends StatefulWidget {
|
class HomeContent extends StatefulWidget {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:travel_mate/models/trip.dart';
|
import 'package:travel_mate/data/models/trip.dart';
|
||||||
|
|
||||||
class ShowTripDetailsContent extends StatefulWidget {
|
class ShowTripDetailsContent extends StatefulWidget {
|
||||||
final Trip trip;
|
final Trip trip;
|
||||||
|
|||||||
0
lib/data/data_sources/firestore_data_source.dart
Normal file
0
lib/data/data_sources/firestore_data_source.dart
Normal file
0
lib/data/data_sources/local_data_source.dart
Normal file
0
lib/data/data_sources/local_data_source.dart
Normal file
126
lib/main.dart
126
lib/main.dart
@@ -1,30 +1,25 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:travel_mate/pages/resetpswd.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:travel_mate/pages/signup.dart';
|
import 'blocs/auth/auth_bloc.dart';
|
||||||
|
import 'blocs/auth/auth_event.dart';
|
||||||
|
import 'blocs/auth/auth_state.dart';
|
||||||
|
import 'blocs/theme/theme_bloc.dart';
|
||||||
|
import 'blocs/theme/theme_event.dart';
|
||||||
|
import 'blocs/theme/theme_state.dart';
|
||||||
|
import 'repositories/auth_repository.dart';
|
||||||
|
import 'repositories/trip_repository.dart';
|
||||||
|
import 'repositories/user_repository.dart';
|
||||||
import 'pages/login.dart';
|
import 'pages/login.dart';
|
||||||
import 'pages/home.dart';
|
import 'pages/home.dart';
|
||||||
import 'providers/theme_provider.dart';
|
import 'pages/signup.dart';
|
||||||
import 'providers/user_provider.dart';
|
import 'pages/resetpswd.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'firebase_options.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp();
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
runApp(const MyApp());
|
||||||
runApp(
|
|
||||||
MultiProvider(
|
|
||||||
providers: [
|
|
||||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
|
||||||
ChangeNotifierProvider(
|
|
||||||
create: (context) => UserProvider(),
|
|
||||||
), // Ajoutez cette ligne
|
|
||||||
],
|
|
||||||
child: const MyApp(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@@ -32,40 +27,67 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<ThemeProvider>(
|
return MultiRepositoryProvider(
|
||||||
builder: (context, themeProvider, child) {
|
providers: [
|
||||||
return MaterialApp(
|
RepositoryProvider<AuthRepository>(
|
||||||
title: 'Travel Mate',
|
create: (context) => AuthRepository(),
|
||||||
themeMode: themeProvider.themeMode,
|
),
|
||||||
|
RepositoryProvider<UserRepository>(
|
||||||
// Thème clair
|
create: (context) => UserRepository(),
|
||||||
theme: ThemeData(
|
),
|
||||||
colorScheme: ColorScheme.fromSeed(
|
RepositoryProvider<TripRepository>(
|
||||||
seedColor: const Color.fromARGB(255, 180, 180, 180),
|
create: (context) => TripRepository(),
|
||||||
brightness: Brightness.light,
|
),
|
||||||
),
|
],
|
||||||
useMaterial3: true,
|
child: MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider<ThemeBloc>(
|
||||||
|
create: (context) => ThemeBloc()..add(ThemeLoadRequested()),
|
||||||
),
|
),
|
||||||
|
BlocProvider<AuthBloc>(
|
||||||
// Thème sombre
|
create: (context) => AuthBloc(
|
||||||
darkTheme: ThemeData(
|
authRepository: context.read<AuthRepository>(),
|
||||||
colorScheme: ColorScheme.fromSeed(
|
)..add(AuthCheckRequested()),
|
||||||
seedColor: const Color.fromARGB(255, 43, 43, 43),
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
),
|
|
||||||
useMaterial3: true,
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
initialRoute: '/login',
|
child: BlocBuilder<ThemeBloc, ThemeState>(
|
||||||
routes: {
|
builder: (context, themeState) {
|
||||||
'/login': (context) => const LoginPage(),
|
return MaterialApp(
|
||||||
'/signup': (context) => const SignUpPage(),
|
title: 'Travel Mate',
|
||||||
'/home': (context) => const HomePage(),
|
themeMode: themeState.themeMode,
|
||||||
'/forgot': (context) => const ForgotPasswordPage(),
|
theme: ThemeData(
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color.fromARGB(255, 180, 180, 180),
|
||||||
|
brightness: Brightness.light,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color.fromARGB(255, 43, 43, 43),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
home: BlocBuilder<AuthBloc, AuthState>(
|
||||||
|
builder: (context, authState) {
|
||||||
|
if (authState is AuthAuthenticated) {
|
||||||
|
return const HomePage();
|
||||||
|
}
|
||||||
|
return const LoginPage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
routes: {
|
||||||
|
'/login': (context) => const LoginPage(),
|
||||||
|
'/signup': (context) => const SignUpPage(),
|
||||||
|
'/home': (context) => const HomePage(),
|
||||||
|
'/forgot': (context) => const ForgotPasswordPage(),
|
||||||
|
},
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
debugShowCheckedModeBanner: false,
|
),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/auth_service.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:provider/provider.dart';
|
import '../blocs/auth/auth_bloc.dart';
|
||||||
import '../providers/user_provider.dart';
|
import '../blocs/auth/auth_event.dart';
|
||||||
|
import '../blocs/auth/auth_state.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
@@ -14,9 +15,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _authService = AuthService();
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -26,339 +24,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthode de connexion
|
|
||||||
Future<void> _login() async {
|
|
||||||
if (!_formKey.currentState!.validate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final userCredential = await _authService.signIn(
|
|
||||||
email: _emailController.text.trim(),
|
|
||||||
password: _passwordController.text,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted && userCredential.user != null) {
|
|
||||||
// Récupérer les données utilisateur depuis Firestore
|
|
||||||
final userProvider = Provider.of<UserProvider>(context, listen: false);
|
|
||||||
final userData = await userProvider.getUserData(userCredential.user!.uid);
|
|
||||||
|
|
||||||
if (userData != null) {
|
|
||||||
userProvider.setCurrentUser(userData);
|
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
|
||||||
} else {
|
|
||||||
_showErrorMessage('Données utilisateur non trouvées');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
_showErrorMessage('Email ou mot de passe incorrect');
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _signInWithGoogle() async {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final userCredential = await _authService.signInWithGoogle();
|
|
||||||
|
|
||||||
if (mounted && userCredential.user != null) {
|
|
||||||
final user = userCredential.user!;
|
|
||||||
final userProvider = Provider.of<UserProvider>(context, listen: false);
|
|
||||||
|
|
||||||
// Récupérer les données utilisateur depuis Firestore
|
|
||||||
final userData = await userProvider.getUserData(user.uid);
|
|
||||||
|
|
||||||
if (userData != null) {
|
|
||||||
// L'utilisateur existe déjà
|
|
||||||
userProvider.setCurrentUser(userData);
|
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
|
||||||
} else {
|
|
||||||
// L'utilisateur n'existe pas, créer son profil
|
|
||||||
final newUserData = {
|
|
||||||
'uid': user.uid,
|
|
||||||
'email': user.email ?? '',
|
|
||||||
'name': user.displayName ?? 'Utilisateur',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Créer le profil utilisateur dans Firestore
|
|
||||||
final createdUser = await userProvider.createUser(newUserData);
|
|
||||||
|
|
||||||
if (createdUser != null) {
|
|
||||||
userProvider.setCurrentUser(createdUser);
|
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
|
||||||
} else {
|
|
||||||
_showErrorMessage('Erreur lors de la création du profil utilisateur');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
_showErrorMessage('Erreur lors de la connexion avec Google: ${e.toString()}');
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
|
|
||||||
// Titre
|
|
||||||
Text(
|
|
||||||
'Travel Mate',
|
|
||||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// Sous-titre
|
|
||||||
Text(
|
|
||||||
'Connectez-vous pour continuer',
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
|
|
||||||
// Champ email
|
|
||||||
TextFormField(
|
|
||||||
controller: _emailController,
|
|
||||||
validator: _validateEmail,
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Email',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.email),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Champ mot de passe
|
|
||||||
TextFormField(
|
|
||||||
controller: _passwordController,
|
|
||||||
validator: _validatePassword,
|
|
||||||
obscureText: _obscurePassword,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Mot de passe',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.lock),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_obscurePassword
|
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_obscurePassword = !_obscurePassword;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
// Lien "Mot de passe oublié"
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pushNamed(context, '/forgot');
|
|
||||||
},
|
|
||||||
child: Text('Mot de passe oublié?'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Bouton de connexion
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 50,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: _isLoading ? null : _login,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: _isLoading
|
|
||||||
? CircularProgressIndicator(color: Colors.white)
|
|
||||||
: Text(
|
|
||||||
'Se connecter',
|
|
||||||
style: TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Lien d'inscription
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text("Vous n'avez pas de compte?"),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pushNamed(context, '/signup');
|
|
||||||
},
|
|
||||||
child: Text('S\'inscrire'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
|
|
||||||
// Séparateur
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 1,
|
|
||||||
color: Colors.grey.shade300,
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Ou connectez-vous avec',
|
|
||||||
style: TextStyle(color: Colors.grey.shade600),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
// GOOGLE
|
|
||||||
GestureDetector(
|
|
||||||
onTap: _isLoading ? null : _signInWithGoogle,
|
|
||||||
child: Container(
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withValues(alpha: 0.3),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 3,
|
|
||||||
offset: Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.grey.shade300,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/icons/google.png',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text('Google'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(width: 40),
|
|
||||||
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
// APPLE
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
// TODO: Implémenter la connexion Apple
|
|
||||||
_showErrorMessage(
|
|
||||||
'Connexion Apple non implémentée',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withValues(alpha: 0.3),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 3,
|
|
||||||
offset: Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.grey.shade300,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/icons/apple_white.png',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text('Apple'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation de l'email
|
|
||||||
String? _validateEmail(String? value) {
|
String? _validateEmail(String? value) {
|
||||||
if (value == null || value.trim().isEmpty) {
|
if (value == null || value.trim().isEmpty) {
|
||||||
return 'Email requis';
|
return 'Email requis';
|
||||||
@@ -370,7 +35,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation du mot de passe
|
|
||||||
String? _validatePassword(String? value) {
|
String? _validatePassword(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Mot de passe requis';
|
return 'Mot de passe requis';
|
||||||
@@ -378,15 +42,281 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showErrorMessage(String message) {
|
void _login(BuildContext context) {
|
||||||
if (mounted) {
|
if (!_formKey.currentState!.validate()) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
return;
|
||||||
SnackBar(
|
|
||||||
content: Text(message),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.read<AuthBloc>().add(
|
||||||
|
AuthSignInRequested(
|
||||||
|
email: _emailController.text.trim(),
|
||||||
|
password: _passwordController.text,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: BlocConsumer<AuthBloc, AuthState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is AuthAuthenticated) {
|
||||||
|
Navigator.pushReplacementNamed(context, '/home');
|
||||||
|
} else if (state is AuthError) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
final isLoading = state is AuthLoading;
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
const Text(
|
||||||
|
'Travel Mate',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'Connectez-vous pour continuer',
|
||||||
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Email
|
||||||
|
TextFormField(
|
||||||
|
controller: _emailController,
|
||||||
|
validator: _validateEmail,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Email',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.email),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Password
|
||||||
|
TextFormField(
|
||||||
|
controller: _passwordController,
|
||||||
|
validator: _validatePassword,
|
||||||
|
obscureText: _obscurePassword,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Mot de passe',
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.lock),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscurePassword
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscurePassword = !_obscurePassword;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Forgot password
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pushNamed(context, '/forgot');
|
||||||
|
},
|
||||||
|
child: const Text('Mot de passe oublié?'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Login button
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: isLoading ? null : () => _login(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: isLoading
|
||||||
|
? const CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'Se connecter',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Sign up link
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text("Vous n'avez pas de compte?"),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pushNamed(context, '/signup');
|
||||||
|
},
|
||||||
|
child: const Text('S\'inscrire'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
// Divider
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 1,
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
const Text(
|
||||||
|
'Ou connectez-vous avec',
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Social login buttons
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Google
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: isLoading
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
context
|
||||||
|
.read<AuthBloc>()
|
||||||
|
.add(AuthGoogleSignInRequested());
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withValues(alpha: 0.3),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/icons/google.png',
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text('Google'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 40),
|
||||||
|
|
||||||
|
// Apple
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: isLoading
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
context
|
||||||
|
.read<AuthBloc>()
|
||||||
|
.add(AuthAppleSignInRequested());
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withValues(alpha: 0.3),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/icons/apple_white.png',
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text('Apple'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../models/user.dart';
|
import '../blocs/auth/auth_bloc.dart';
|
||||||
import '../services/auth_service.dart';
|
import '../blocs/auth/auth_event.dart';
|
||||||
import '../providers/user_provider.dart';
|
import '../blocs/auth/auth_state.dart';
|
||||||
|
|
||||||
class SignUpPage extends StatefulWidget {
|
class SignUpPage extends StatefulWidget {
|
||||||
const SignUpPage({super.key});
|
const SignUpPage({super.key});
|
||||||
@@ -18,9 +18,7 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _confirmPasswordController = TextEditingController();
|
final _confirmPasswordController = TextEditingController();
|
||||||
final _authService = AuthService();
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
bool _obscureConfirmPassword = true;
|
bool _obscureConfirmPassword = true;
|
||||||
|
|
||||||
@@ -34,7 +32,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthode de validation
|
|
||||||
String? _validateField(String? value, String fieldName) {
|
String? _validateField(String? value, String fieldName) {
|
||||||
if (value == null || value.trim().isEmpty) {
|
if (value == null || value.trim().isEmpty) {
|
||||||
return '$fieldName est requis';
|
return '$fieldName est requis';
|
||||||
@@ -46,7 +43,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
if (value == null || value.trim().isEmpty) {
|
if (value == null || value.trim().isEmpty) {
|
||||||
return 'Email est requis';
|
return 'Email est requis';
|
||||||
}
|
}
|
||||||
|
|
||||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||||
if (!emailRegex.hasMatch(value.trim())) {
|
if (!emailRegex.hasMatch(value.trim())) {
|
||||||
return 'Email invalide';
|
return 'Email invalide';
|
||||||
@@ -74,86 +70,19 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthode d'enregistrement
|
void _signUp(BuildContext context) {
|
||||||
Future<void> _signUp() async {
|
|
||||||
if (!_formKey.currentState!.validate()) {
|
if (!_formKey.currentState!.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
context.read<AuthBloc>().add(
|
||||||
_isLoading = true;
|
AuthSignUpRequested(
|
||||||
});
|
email: _emailController.text.trim(),
|
||||||
|
password: _passwordController.text,
|
||||||
try {
|
nom: _nomController.text.trim(),
|
||||||
// Créer le compte avec Firebase Auth
|
prenom: _prenomController.text.trim(),
|
||||||
final userCredential = await _authService.createAccount(
|
),
|
||||||
email: _emailController.text.trim(),
|
|
||||||
password: _passwordController.text,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Créer l'objet User
|
|
||||||
User newUser = User(
|
|
||||||
id: userCredential.user!.uid,
|
|
||||||
nom: _nomController.text.trim(),
|
|
||||||
prenom: _prenomController.text.trim(),
|
|
||||||
email: _emailController.text.trim(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sauvegarder les données utilisateur dans Firestore
|
|
||||||
await Provider.of<UserProvider>(context, listen: false).saveUserData(newUser);
|
|
||||||
|
|
||||||
// Mettre à jour le displayName
|
|
||||||
await _authService.updateDisplayName(displayName: newUser.fullName);
|
|
||||||
|
|
||||||
_showSuccessDialog();
|
|
||||||
} catch (e) {
|
|
||||||
_showErrorDialog('Erreur lors de la création du compte: ${e.toString()}');
|
|
||||||
} finally {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showSuccessDialog() {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text('Succès'),
|
|
||||||
content: Text('Votre compte a été créé avec succès !'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(); // Fermer la dialog
|
|
||||||
Navigator.of(context).pop(); // Retourner à la page de login
|
|
||||||
},
|
|
||||||
child: Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showErrorDialog(String message) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text('Erreur'),
|
|
||||||
content: Text(message),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -161,185 +90,194 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: BlocConsumer<AuthBloc, AuthState>(
|
||||||
child: SingleChildScrollView(
|
listener: (context, state) {
|
||||||
child: Padding(
|
if (state is AuthAuthenticated) {
|
||||||
padding: const EdgeInsets.all(16.0),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
child: Form(
|
const SnackBar(
|
||||||
key: _formKey,
|
content: Text('Compte créé avec succès !'),
|
||||||
child: Column(
|
backgroundColor: Colors.green,
|
||||||
children: [
|
),
|
||||||
const SizedBox(height: 40),
|
);
|
||||||
|
Navigator.pushReplacementNamed(context, '/home');
|
||||||
|
} else if (state is AuthError) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
final isLoading = state is AuthLoading;
|
||||||
|
|
||||||
const Text(
|
return SafeArea(
|
||||||
'Créer un compte',
|
child: SingleChildScrollView(
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Form(
|
||||||
const SizedBox(height: 12),
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
const Text(
|
|
||||||
'Rejoignez Travel Mate',
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
|
|
||||||
// Champ Prénom
|
|
||||||
TextFormField(
|
|
||||||
controller: _prenomController,
|
|
||||||
validator: (value) => _validateField(value, 'Prénom'),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Prénom',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.person_outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Champ Nom
|
|
||||||
TextFormField(
|
|
||||||
controller: _nomController,
|
|
||||||
validator: (value) => _validateField(value, 'Nom'),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Nom',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.person),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Champ Email
|
|
||||||
TextFormField(
|
|
||||||
controller: _emailController,
|
|
||||||
validator: _validateEmail,
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Email',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.email),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Champ Mot de passe
|
|
||||||
TextFormField(
|
|
||||||
controller: _passwordController,
|
|
||||||
validator: _validatePassword,
|
|
||||||
obscureText: _obscurePassword,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Mot de passe',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.lock),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_obscurePassword
|
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_obscurePassword = !_obscurePassword;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Champ Confirmation mot de passe
|
|
||||||
TextFormField(
|
|
||||||
controller: _confirmPasswordController,
|
|
||||||
validator: _validateConfirmPassword,
|
|
||||||
obscureText: _obscureConfirmPassword,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Confirmez le mot de passe',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(Icons.lock_outline),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_obscureConfirmPassword
|
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_obscureConfirmPassword = !_obscureConfirmPassword;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
|
|
||||||
// Bouton d'inscription
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 50,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: _isLoading ? null : _signUp,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: _isLoading
|
|
||||||
? CircularProgressIndicator(color: Colors.white)
|
|
||||||
: Text('S\'inscrire', style: TextStyle(fontSize: 18)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Lien vers login
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
const Text("Déjà un compte ? "),
|
const SizedBox(height: 40),
|
||||||
GestureDetector(
|
const Text(
|
||||||
onTap: () {
|
'Créer un compte',
|
||||||
Navigator.pop(context);
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
},
|
),
|
||||||
child: const Text(
|
const SizedBox(height: 12),
|
||||||
'Connectez-vous !',
|
const Text(
|
||||||
style: TextStyle(
|
'Rejoignez Travel Mate',
|
||||||
color: Colors.blue,
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
// Champ Prénom
|
||||||
|
TextFormField(
|
||||||
|
controller: _prenomController,
|
||||||
|
validator: (value) => _validateField(value, 'Prénom'),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Prénom',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.person_outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Champ Nom
|
||||||
|
TextFormField(
|
||||||
|
controller: _nomController,
|
||||||
|
validator: (value) => _validateField(value, 'Nom'),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Nom',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.person),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Champ Email
|
||||||
|
TextFormField(
|
||||||
|
controller: _emailController,
|
||||||
|
validator: _validateEmail,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Email',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.email),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Champ Mot de passe
|
||||||
|
TextFormField(
|
||||||
|
controller: _passwordController,
|
||||||
|
validator: _validatePassword,
|
||||||
|
obscureText: _obscurePassword,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Mot de passe',
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.lock),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscurePassword
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscurePassword = !_obscurePassword;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Champ Confirmation mot de passe
|
||||||
|
TextFormField(
|
||||||
|
controller: _confirmPasswordController,
|
||||||
|
validator: _validateConfirmPassword,
|
||||||
|
obscureText: _obscureConfirmPassword,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Confirmez le mot de passe',
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.lock_outline),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscureConfirmPassword
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscureConfirmPassword = !_obscureConfirmPassword;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Bouton d'inscription
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: isLoading ? null : () => _signUp(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: isLoading
|
||||||
|
? const CircularProgressIndicator(color: Colors.white)
|
||||||
|
: const Text('S\'inscrire', style: TextStyle(fontSize: 18)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Lien vers login
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text("Déjà un compte ? "),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
child: const Text(
|
||||||
|
'Connectez-vous !',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
148
lib/repositories/auth_repository.dart
Normal file
148
lib/repositories/auth_repository.dart
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import '../data/models/user.dart';
|
||||||
|
import '../services/auth_service.dart';
|
||||||
|
|
||||||
|
class AuthRepository {
|
||||||
|
final AuthService _authService;
|
||||||
|
final FirebaseFirestore _firestore;
|
||||||
|
|
||||||
|
AuthRepository({
|
||||||
|
AuthService? authService,
|
||||||
|
FirebaseFirestore? firestore,
|
||||||
|
}) : _authService = authService ?? AuthService(),
|
||||||
|
_firestore = firestore ?? FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
// Vérifier l'état de connexion actuel
|
||||||
|
Stream<firebase_auth.User?> get authStateChanges =>
|
||||||
|
_authService.authStateChanges;
|
||||||
|
|
||||||
|
firebase_auth.User? get currentUser => _authService.currentUser;
|
||||||
|
|
||||||
|
// Connexion avec email/mot de passe
|
||||||
|
Future<User?> signInWithEmailAndPassword({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final firebaseUser = await _authService.signInWithEmailAndPassword(
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
return await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur de connexion: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inscription avec email/mot de passe
|
||||||
|
Future<User?> signUpWithEmailAndPassword({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
required String nom,
|
||||||
|
required String prenom,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final firebaseUser = await _authService.signUpWithEmailAndPassword(
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Créer le document utilisateur dans Firestore
|
||||||
|
final user = User(
|
||||||
|
id: firebaseUser.user!.uid,
|
||||||
|
email: email,
|
||||||
|
nom: nom,
|
||||||
|
prenom: prenom,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
return user;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur d\'inscription: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connexion avec Google
|
||||||
|
Future<User?> signInWithGoogle() async {
|
||||||
|
try {
|
||||||
|
final firebaseUser = await _authService.signInWithGoogle();
|
||||||
|
|
||||||
|
if (firebaseUser.user != null) {
|
||||||
|
// Vérifier si l'utilisateur existe déjà
|
||||||
|
final existingUser = await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
|
|
||||||
|
if (existingUser != null) {
|
||||||
|
return existingUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer un nouvel utilisateur
|
||||||
|
final user = User(
|
||||||
|
id: firebaseUser.user!.uid,
|
||||||
|
email: firebaseUser.user!.email ?? '',
|
||||||
|
nom: '',
|
||||||
|
prenom: firebaseUser.user!.displayName ?? 'Utilisateur',
|
||||||
|
);
|
||||||
|
|
||||||
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur de connexion Google: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connexion avec Apple
|
||||||
|
Future<User?> signInWithApple() async {
|
||||||
|
try {
|
||||||
|
final firebaseUser = await _authService.signInWithApple();
|
||||||
|
|
||||||
|
if (firebaseUser?.user != null) {
|
||||||
|
final existingUser = await getUserFromFirestore(firebaseUser!.user!.uid);
|
||||||
|
|
||||||
|
if (existingUser != null) {
|
||||||
|
return existingUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
final user = User(
|
||||||
|
id: firebaseUser.user!.uid,
|
||||||
|
email: firebaseUser.user!.email ?? '',
|
||||||
|
nom: '',
|
||||||
|
prenom: firebaseUser.user!.displayName ?? 'Utilisateur',
|
||||||
|
);
|
||||||
|
|
||||||
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur de connexion Apple: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Déconnexion
|
||||||
|
Future<void> signOut() async {
|
||||||
|
await _authService.signOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Réinitialisation du mot de passe
|
||||||
|
Future<void> resetPassword(String email) async {
|
||||||
|
await _authService.resetPassword(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les données utilisateur depuis Firestore
|
||||||
|
Future<User?> getUserFromFirestore(String uid) async {
|
||||||
|
try {
|
||||||
|
final doc = await _firestore.collection('users').doc(uid).get();
|
||||||
|
if (doc.exists) {
|
||||||
|
final data = doc.data() as Map<String, dynamic>;
|
||||||
|
return User.fromMap({...data, 'id': uid});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
lib/repositories/trip_repository.dart
Normal file
113
lib/repositories/trip_repository.dart
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import '../data/models/trip.dart';
|
||||||
|
|
||||||
|
class TripRepository {
|
||||||
|
final FirebaseFirestore _firestore;
|
||||||
|
|
||||||
|
TripRepository({FirebaseFirestore? firestore})
|
||||||
|
: _firestore = firestore ?? FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
// Créer un voyage
|
||||||
|
Future<Trip> createTrip(Trip trip) async {
|
||||||
|
try {
|
||||||
|
final docRef = await _firestore.collection('trips').add(trip.toMap());
|
||||||
|
final createdTrip = trip.copyWith(id: docRef.id);
|
||||||
|
|
||||||
|
// Mettre à jour avec l'ID généré
|
||||||
|
await docRef.update({'id': docRef.id});
|
||||||
|
|
||||||
|
return createdTrip;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la création du voyage: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les voyages d'un utilisateur
|
||||||
|
Stream<List<Trip>> getUserTrips(String userId) {
|
||||||
|
return _firestore
|
||||||
|
.collection('trips')
|
||||||
|
.where('createdBy', isEqualTo: userId)
|
||||||
|
.snapshots()
|
||||||
|
.map((snapshot) {
|
||||||
|
return snapshot.docs.map((doc) {
|
||||||
|
final data = doc.data();
|
||||||
|
return Trip.fromMap({...data, 'id': doc.id});
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les voyages où l'utilisateur est participant
|
||||||
|
Stream<List<Trip>> getSharedTrips(String userId) {
|
||||||
|
return _firestore
|
||||||
|
.collection('trips')
|
||||||
|
.where('participants', arrayContains: userId)
|
||||||
|
.snapshots()
|
||||||
|
.map((snapshot) {
|
||||||
|
return snapshot.docs.map((doc) {
|
||||||
|
final data = doc.data();
|
||||||
|
return Trip.fromMap({...data, 'id': doc.id});
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer un voyage par ID
|
||||||
|
Future<Trip?> getTripById(String tripId) async {
|
||||||
|
try {
|
||||||
|
final doc = await _firestore.collection('trips').doc(tripId).get();
|
||||||
|
if (doc.exists) {
|
||||||
|
final data = doc.data() as Map<String, dynamic>;
|
||||||
|
return Trip.fromMap({...data, 'id': doc.id});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la récupération du voyage: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettre à jour un voyage
|
||||||
|
Future<bool> updateTrip(Trip trip) async {
|
||||||
|
try {
|
||||||
|
await _firestore
|
||||||
|
.collection('trips')
|
||||||
|
.doc(trip.id)
|
||||||
|
.update(trip.toMap());
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la mise à jour du voyage: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer un voyage
|
||||||
|
Future<bool> deleteTrip(String tripId) async {
|
||||||
|
try {
|
||||||
|
await _firestore.collection('trips').doc(tripId).delete();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la suppression du voyage: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter un participant
|
||||||
|
Future<bool> addParticipant(String tripId, String participantEmail) async {
|
||||||
|
try {
|
||||||
|
await _firestore.collection('trips').doc(tripId).update({
|
||||||
|
'participants': FieldValue.arrayUnion([participantEmail])
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de l\'ajout du participant: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retirer un participant
|
||||||
|
Future<bool> removeParticipant(String tripId, String participantEmail) async {
|
||||||
|
try {
|
||||||
|
await _firestore.collection('trips').doc(tripId).update({
|
||||||
|
'participants': FieldValue.arrayRemove([participantEmail])
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors du retrait du participant: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
lib/repositories/user_repository.dart
Normal file
95
lib/repositories/user_repository.dart
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import '../data/models/user.dart';
|
||||||
|
import '../services/auth_service.dart';
|
||||||
|
|
||||||
|
class UserRepository {
|
||||||
|
final FirebaseFirestore _firestore;
|
||||||
|
final AuthService _authService;
|
||||||
|
|
||||||
|
UserRepository({
|
||||||
|
FirebaseFirestore? firestore,
|
||||||
|
AuthService? authService,
|
||||||
|
}) : _firestore = firestore ?? FirebaseFirestore.instance,
|
||||||
|
_authService = authService ?? AuthService();
|
||||||
|
|
||||||
|
// Récupérer un utilisateur par ID
|
||||||
|
Future<User?> getUserById(String uid) async {
|
||||||
|
try {
|
||||||
|
final doc = await _firestore.collection('users').doc(uid).get();
|
||||||
|
if (doc.exists) {
|
||||||
|
final data = doc.data() as Map<String, dynamic>;
|
||||||
|
return User.fromMap({...data, 'id': uid});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la récupération de l\'utilisateur: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer un utilisateur par email
|
||||||
|
Future<User?> getUserByEmail(String email) async {
|
||||||
|
try {
|
||||||
|
final querySnapshot = await _firestore
|
||||||
|
.collection('users')
|
||||||
|
.where('email', isEqualTo: email.trim())
|
||||||
|
.limit(1)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (querySnapshot.docs.isNotEmpty) {
|
||||||
|
final doc = querySnapshot.docs.first;
|
||||||
|
final data = doc.data();
|
||||||
|
return User.fromMap({...data, 'id': doc.id});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la recherche de l\'utilisateur: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettre à jour un utilisateur
|
||||||
|
Future<bool> updateUser(User user) async {
|
||||||
|
try {
|
||||||
|
await _firestore.collection('users').doc(user.id).update(user.toMap());
|
||||||
|
|
||||||
|
// Mettre à jour le displayName dans Firebase Auth
|
||||||
|
await _authService.updateDisplayName(displayName: user.fullName);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la mise à jour: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer un utilisateur
|
||||||
|
Future<bool> deleteUser(String uid) async {
|
||||||
|
try {
|
||||||
|
await _firestore.collection('users').doc(uid).delete();
|
||||||
|
// Note: Vous devrez également supprimer le compte Firebase Auth
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la suppression: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changer le mot de passe
|
||||||
|
Future<bool> changePassword({
|
||||||
|
required String currentPassword,
|
||||||
|
required String newPassword,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final currentUser = _authService.currentUser;
|
||||||
|
if (currentUser?.email == null) {
|
||||||
|
throw Exception('Utilisateur non connecté ou email non disponible');
|
||||||
|
}
|
||||||
|
|
||||||
|
await _authService.resetPasswordFromCurrentPassword(
|
||||||
|
email: currentUser!.email!,
|
||||||
|
currentPassword: currentPassword,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors du changement de mot de passe: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ class AuthService {
|
|||||||
|
|
||||||
Stream<User?> get authStateChanges => firebaseAuth.authStateChanges();
|
Stream<User?> get authStateChanges => firebaseAuth.authStateChanges();
|
||||||
|
|
||||||
Future<UserCredential> signIn({
|
Future<UserCredential> signInWithEmailAndPassword({
|
||||||
required String email,
|
required String email,
|
||||||
required String password
|
required String password
|
||||||
}) async {
|
}) async {
|
||||||
@@ -17,7 +17,7 @@ class AuthService {
|
|||||||
email: email, password: password);
|
email: email, password: password);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserCredential> createAccount({
|
Future<UserCredential> signUpWithEmailAndPassword({
|
||||||
required String email,
|
required String email,
|
||||||
required String password
|
required String password
|
||||||
}) async {
|
}) async {
|
||||||
@@ -100,5 +100,9 @@ class AuthService {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future signInWithApple() async {
|
||||||
|
// TODO: Implémenter la connexion avec Apple
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import '../models/trip.dart';
|
import '../data/models/trip.dart';
|
||||||
|
|
||||||
class TripService {
|
class TripService {
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
@@ -53,26 +53,19 @@ class TripService {
|
|||||||
.collection(_tripsCollection)
|
.collection(_tripsCollection)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map((snapshot) {
|
.map((snapshot) {
|
||||||
print('=== NOUVEAU SNAPSHOT ===');
|
|
||||||
print('Nombre de documents: ${snapshot.docs.length}');
|
|
||||||
|
|
||||||
final List<Trip> trips = [];
|
final List<Trip> trips = [];
|
||||||
|
|
||||||
for (int i = 0; i < snapshot.docs.length; i++) {
|
for (int i = 0; i < snapshot.docs.length; i++) {
|
||||||
var doc = snapshot.docs[i];
|
var doc = snapshot.docs[i];
|
||||||
print('\n--- Document $i (${doc.id}) ---');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final data = doc.data() as Map<String, dynamic>;
|
final data = doc.data();
|
||||||
|
|
||||||
// Vérifier si l'utilisateur est impliqué dans ce voyage
|
// Vérifier si l'utilisateur est impliqué dans ce voyage
|
||||||
final String createdBy = data['createdBy']?.toString() ?? '';
|
final String createdBy = data['createdBy']?.toString() ?? '';
|
||||||
final List<dynamic> participants = data['participants'] ?? [];
|
final List<dynamic> participants = data['participants'] ?? [];
|
||||||
|
|
||||||
print('CreatedBy: "$createdBy"');
|
|
||||||
print('UserId: "$userId"');
|
|
||||||
print('Participants: $participants');
|
|
||||||
|
|
||||||
bool userIsInvolved = false;
|
bool userIsInvolved = false;
|
||||||
String reason = '';
|
String reason = '';
|
||||||
|
|
||||||
@@ -88,20 +81,11 @@ class TripService {
|
|||||||
reason = reason.isEmpty ? 'Participant par ID' : '$reason + Participant par ID';
|
reason = reason.isEmpty ? 'Participant par ID' : '$reason + Participant par ID';
|
||||||
}
|
}
|
||||||
|
|
||||||
print('Utilisateur impliqué: $userIsInvolved');
|
|
||||||
print('Raison: $reason');
|
|
||||||
|
|
||||||
if (userIsInvolved) {
|
if (userIsInvolved) {
|
||||||
print('Tentative de conversion du trip...');
|
|
||||||
final trip = _convertDocumentToTrip(doc.id, data);
|
final trip = _convertDocumentToTrip(doc.id, data);
|
||||||
if (trip != null) {
|
if (trip != null) {
|
||||||
trips.add(trip);
|
trips.add(trip);
|
||||||
print('Trip ajouté: ${trip.title}');
|
|
||||||
} else {
|
|
||||||
print('Échec de la conversion du trip');
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
print('Utilisateur non impliqué dans ce voyage');
|
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('Erreur lors du traitement du document ${doc.id}: $e');
|
print('Erreur lors du traitement du document ${doc.id}: $e');
|
||||||
@@ -109,14 +93,6 @@ class TripService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print('\n=== RÉSUMÉ ===');
|
|
||||||
print('Trips trouvés: ${trips.length}');
|
|
||||||
if (trips.isNotEmpty) {
|
|
||||||
for (int i = 0; i < trips.length; i++) {
|
|
||||||
print(' ${i+1}. ${trips[i].title} (${trips[i].id})');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trier par date de création (les plus récents en premier)
|
// Trier par date de création (les plus récents en premier)
|
||||||
trips.sort((a, b) {
|
trips.sort((a, b) {
|
||||||
try {
|
try {
|
||||||
@@ -138,8 +114,6 @@ class TripService {
|
|||||||
// Obtenir les voyages d'un utilisateur (version simplifiée)
|
// Obtenir les voyages d'un utilisateur (version simplifiée)
|
||||||
Future<List<Trip>> getTripsByUser(String userId) async {
|
Future<List<Trip>> getTripsByUser(String userId) async {
|
||||||
try {
|
try {
|
||||||
print('Récupération des voyages pour userId: $userId');
|
|
||||||
|
|
||||||
// Récupérer d'abord les voyages créés par l'utilisateur
|
// Récupérer d'abord les voyages créés par l'utilisateur
|
||||||
final QuerySnapshot createdTrips = await _firestore
|
final QuerySnapshot createdTrips = await _firestore
|
||||||
.collection(_tripsCollection)
|
.collection(_tripsCollection)
|
||||||
@@ -176,9 +150,7 @@ class TripService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Méthode helper pour convertir un document Firestore en Trip
|
// Méthode helper pour convertir un document Firestore en Trip
|
||||||
Trip? _convertDocumentToTrip(String docId, Map<String, dynamic> data) {
|
Trip? _convertDocumentToTrip(String docId, Map<String, dynamic> data) {
|
||||||
print('\n=== CONVERSION TRIP $docId ===');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Créer une copie des données pour ne pas modifier l'original
|
// Créer une copie des données pour ne pas modifier l'original
|
||||||
Map<String, dynamic> processedData = Map<String, dynamic>.from(data);
|
Map<String, dynamic> processedData = Map<String, dynamic>.from(data);
|
||||||
@@ -211,7 +183,7 @@ class TripService {
|
|||||||
return trip;
|
return trip;
|
||||||
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('❌ Erreur lors de la conversion du document $docId: $e');
|
print('Erreur lors de la conversion du document $docId: $e');
|
||||||
print('StackTrace: $stackTrace');
|
print('StackTrace: $stackTrace');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
24
pubspec.lock
24
pubspec.lock
@@ -25,6 +25,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.1.3"
|
||||||
|
bloc:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bloc
|
||||||
|
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.4"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -97,6 +105,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
|
equatable:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -174,6 +190,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_bloc:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_bloc
|
||||||
|
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.6"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ dependencies:
|
|||||||
path_provider: ^2.1.1
|
path_provider: ^2.1.1
|
||||||
bcrypt: ^1.1.3
|
bcrypt: ^1.1.3
|
||||||
location: ^5.0.0
|
location: ^5.0.0
|
||||||
|
flutter_bloc : ^8.1.3
|
||||||
|
equatable: ^2.0.5
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Reference in New Issue
Block a user