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: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/services/trip_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 {
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:travel_mate/components/home/create_trip_content.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../services/trip_service.dart';
|
||||
import '../../models/trip.dart';
|
||||
import '../../data/models/trip.dart';
|
||||
import '../home/show_trip_details_content.dart';
|
||||
|
||||
class HomeContent extends StatefulWidget {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:travel_mate/models/trip.dart';
|
||||
import 'package:travel_mate/data/models/trip.dart';
|
||||
|
||||
class ShowTripDetailsContent extends StatefulWidget {
|
||||
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
@@ -1,30 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:travel_mate/pages/resetpswd.dart';
|
||||
import 'package:travel_mate/pages/signup.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:firebase_core/firebase_core.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/home.dart';
|
||||
import 'providers/theme_provider.dart';
|
||||
import 'providers/user_provider.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'pages/signup.dart';
|
||||
import 'pages/resetpswd.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => UserProvider(),
|
||||
), // Ajoutez cette ligne
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
await Firebase.initializeApp();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@@ -32,13 +27,34 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ThemeProvider>(
|
||||
builder: (context, themeProvider, child) {
|
||||
return MultiRepositoryProvider(
|
||||
providers: [
|
||||
RepositoryProvider<AuthRepository>(
|
||||
create: (context) => AuthRepository(),
|
||||
),
|
||||
RepositoryProvider<UserRepository>(
|
||||
create: (context) => UserRepository(),
|
||||
),
|
||||
RepositoryProvider<TripRepository>(
|
||||
create: (context) => TripRepository(),
|
||||
),
|
||||
],
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ThemeBloc>(
|
||||
create: (context) => ThemeBloc()..add(ThemeLoadRequested()),
|
||||
),
|
||||
BlocProvider<AuthBloc>(
|
||||
create: (context) => AuthBloc(
|
||||
authRepository: context.read<AuthRepository>(),
|
||||
)..add(AuthCheckRequested()),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<ThemeBloc, ThemeState>(
|
||||
builder: (context, themeState) {
|
||||
return MaterialApp(
|
||||
title: 'Travel Mate',
|
||||
themeMode: themeProvider.themeMode,
|
||||
|
||||
// Thème clair
|
||||
themeMode: themeState.themeMode,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color.fromARGB(255, 180, 180, 180),
|
||||
@@ -46,8 +62,6 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
|
||||
// Thème sombre
|
||||
darkTheme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color.fromARGB(255, 43, 43, 43),
|
||||
@@ -55,8 +69,14 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
|
||||
initialRoute: '/login',
|
||||
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(),
|
||||
@@ -66,6 +86,8 @@ class MyApp extends StatelessWidget {
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/user_provider.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/auth/auth_bloc.dart';
|
||||
import '../blocs/auth/auth_event.dart';
|
||||
import '../blocs/auth/auth_state.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
@@ -14,9 +15,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _authService = AuthService();
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
@@ -26,102 +24,57 @@ class _LoginPageState extends State<LoginPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Méthode de connexion
|
||||
Future<void> _login() async {
|
||||
String? _validateEmail(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Email requis';
|
||||
}
|
||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
if (!emailRegex.hasMatch(value.trim())) {
|
||||
return 'Email invalide';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Mot de passe requis';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _login(BuildContext context) {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final userCredential = await _authService.signIn(
|
||||
context.read<AuthBloc>().add(
|
||||
AuthSignInRequested(
|
||||
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(
|
||||
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),
|
||||
@@ -131,29 +84,26 @@ class _LoginPageState extends State<LoginPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Titre
|
||||
Text(
|
||||
const Text(
|
||||
'Travel Mate',
|
||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Sous-titre
|
||||
Text(
|
||||
const Text(
|
||||
'Connectez-vous pour continuer',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Champ email
|
||||
// Email
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
validator: _validateEmail,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Email',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
@@ -164,17 +114,17 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Champ mot de passe
|
||||
// Password
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
validator: _validatePassword,
|
||||
obscureText: _obscurePassword,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Mot de passe',
|
||||
border: OutlineInputBorder(
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
@@ -192,33 +142,35 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Lien "Mot de passe oublié"
|
||||
// Forgot password
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/forgot');
|
||||
},
|
||||
child: Text('Mot de passe oublié?'),
|
||||
child: const Text('Mot de passe oublié?'),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Bouton de connexion
|
||||
// Login button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: _isLoading ? null : _login,
|
||||
onPressed: isLoading ? null : () => _login(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? CircularProgressIndicator(color: Colors.white)
|
||||
: Text(
|
||||
child: isLoading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Text(
|
||||
'Se connecter',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
@@ -227,23 +179,23 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Lien d'inscription
|
||||
// Sign up link
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("Vous n'avez pas de compte?"),
|
||||
const Text("Vous n'avez pas de compte?"),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/signup');
|
||||
},
|
||||
child: Text('S\'inscrire'),
|
||||
child: const Text('S\'inscrire'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Séparateur
|
||||
// Divider
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
@@ -252,21 +204,28 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
Text(
|
||||
const Text(
|
||||
'Ou connectez-vous avec',
|
||||
style: TextStyle(color: Colors.grey.shade600),
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Social login buttons
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Google
|
||||
Column(
|
||||
children: [
|
||||
// GOOGLE
|
||||
GestureDetector(
|
||||
onTap: _isLoading ? null : _signInWithGoogle,
|
||||
onTap: isLoading
|
||||
? null
|
||||
: () {
|
||||
context
|
||||
.read<AuthBloc>()
|
||||
.add(AuthGoogleSignInRequested());
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
@@ -278,7 +237,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
color: Colors.grey.withValues(alpha: 0.3),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: Offset(0, 1),
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
@@ -302,15 +261,16 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
const SizedBox(width: 40),
|
||||
|
||||
// Apple
|
||||
Column(
|
||||
children: [
|
||||
// APPLE
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// TODO: Implémenter la connexion Apple
|
||||
_showErrorMessage(
|
||||
'Connexion Apple non implémentée',
|
||||
);
|
||||
onTap: isLoading
|
||||
? null
|
||||
: () {
|
||||
context
|
||||
.read<AuthBloc>()
|
||||
.add(AuthAppleSignInRequested());
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
@@ -323,7 +283,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
color: Colors.grey.withValues(alpha: 0.3),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: Offset(0, 1),
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
@@ -354,39 +314,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Validation de l'email
|
||||
String? _validateEmail(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Email requis';
|
||||
}
|
||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
if (!emailRegex.hasMatch(value.trim())) {
|
||||
return 'Email invalide';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validation du mot de passe
|
||||
String? _validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Mot de passe requis';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _showErrorMessage(String message) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 3),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../models/user.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../providers/user_provider.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/auth/auth_bloc.dart';
|
||||
import '../blocs/auth/auth_event.dart';
|
||||
import '../blocs/auth/auth_state.dart';
|
||||
|
||||
class SignUpPage extends StatefulWidget {
|
||||
const SignUpPage({super.key});
|
||||
@@ -18,9 +18,7 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _confirmPasswordController = TextEditingController();
|
||||
final _authService = AuthService();
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _obscurePassword = true;
|
||||
bool _obscureConfirmPassword = true;
|
||||
|
||||
@@ -34,7 +32,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Méthode de validation
|
||||
String? _validateField(String? value, String fieldName) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return '$fieldName est requis';
|
||||
@@ -46,7 +43,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Email est requis';
|
||||
}
|
||||
|
||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
if (!emailRegex.hasMatch(value.trim())) {
|
||||
return 'Email invalide';
|
||||
@@ -74,85 +70,18 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Méthode d'enregistrement
|
||||
Future<void> _signUp() async {
|
||||
void _signUp(BuildContext context) {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// Créer le compte avec Firebase Auth
|
||||
final userCredential = await _authService.createAccount(
|
||||
context.read<AuthBloc>().add(
|
||||
AuthSignUpRequested(
|
||||
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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,15 +90,35 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
),
|
||||
body: SafeArea(
|
||||
body: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthAuthenticated) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Compte créé avec succès !'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
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(16.0),
|
||||
@@ -178,26 +127,22 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
|
||||
const Text(
|
||||
'Créer un compte',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
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(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Prénom',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
@@ -205,14 +150,13 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
prefixIcon: Icon(Icons.person_outline),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Champ Nom
|
||||
TextFormField(
|
||||
controller: _nomController,
|
||||
validator: (value) => _validateField(value, 'Nom'),
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Nom',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
@@ -220,7 +164,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
prefixIcon: Icon(Icons.person),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Champ Email
|
||||
@@ -228,7 +171,7 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
controller: _emailController,
|
||||
validator: _validateEmail,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Email',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
@@ -236,7 +179,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
prefixIcon: Icon(Icons.email),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Champ Mot de passe
|
||||
@@ -246,10 +188,10 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
obscureText: _obscurePassword,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Mot de passe',
|
||||
border: OutlineInputBorder(
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
@@ -264,7 +206,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Champ Confirmation mot de passe
|
||||
@@ -274,10 +215,10 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
obscureText: _obscureConfirmPassword,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Confirmez le mot de passe',
|
||||
border: OutlineInputBorder(
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
prefixIcon: Icon(Icons.lock_outline),
|
||||
prefixIcon: const Icon(Icons.lock_outline),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscureConfirmPassword
|
||||
@@ -292,7 +233,6 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Bouton d'inscription
|
||||
@@ -300,18 +240,17 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: _isLoading ? null : _signUp,
|
||||
onPressed: isLoading ? null : () => _signUp(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? CircularProgressIndicator(color: Colors.white)
|
||||
: Text('S\'inscrire', style: TextStyle(fontSize: 18)),
|
||||
child: isLoading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: const Text('S\'inscrire', style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Lien vers login
|
||||
@@ -320,9 +259,7 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
children: [
|
||||
const Text("Déjà un compte ? "),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Text(
|
||||
'Connectez-vous !',
|
||||
style: TextStyle(
|
||||
@@ -338,8 +275,9 @@ class _SignUpPageState extends State<SignUpPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Future<UserCredential> signIn({
|
||||
Future<UserCredential> signInWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password
|
||||
}) async {
|
||||
@@ -17,7 +17,7 @@ class AuthService {
|
||||
email: email, password: password);
|
||||
}
|
||||
|
||||
Future<UserCredential> createAccount({
|
||||
Future<UserCredential> signUpWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password
|
||||
}) async {
|
||||
@@ -101,4 +101,8 @@ class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
Future signInWithApple() async {
|
||||
// TODO: Implémenter la connexion avec Apple
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import '../models/trip.dart';
|
||||
import '../data/models/trip.dart';
|
||||
|
||||
class TripService {
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
@@ -53,26 +53,19 @@ class TripService {
|
||||
.collection(_tripsCollection)
|
||||
.snapshots()
|
||||
.map((snapshot) {
|
||||
print('=== NOUVEAU SNAPSHOT ===');
|
||||
print('Nombre de documents: ${snapshot.docs.length}');
|
||||
|
||||
final List<Trip> trips = [];
|
||||
|
||||
for (int i = 0; i < snapshot.docs.length; i++) {
|
||||
var doc = snapshot.docs[i];
|
||||
print('\n--- Document $i (${doc.id}) ---');
|
||||
|
||||
try {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
final data = doc.data();
|
||||
|
||||
// Vérifier si l'utilisateur est impliqué dans ce voyage
|
||||
final String createdBy = data['createdBy']?.toString() ?? '';
|
||||
final List<dynamic> participants = data['participants'] ?? [];
|
||||
|
||||
print('CreatedBy: "$createdBy"');
|
||||
print('UserId: "$userId"');
|
||||
print('Participants: $participants');
|
||||
|
||||
bool userIsInvolved = false;
|
||||
String reason = '';
|
||||
|
||||
@@ -88,20 +81,11 @@ class TripService {
|
||||
reason = reason.isEmpty ? 'Participant par ID' : '$reason + Participant par ID';
|
||||
}
|
||||
|
||||
print('Utilisateur impliqué: $userIsInvolved');
|
||||
print('Raison: $reason');
|
||||
|
||||
if (userIsInvolved) {
|
||||
print('Tentative de conversion du trip...');
|
||||
final trip = _convertDocumentToTrip(doc.id, data);
|
||||
if (trip != null) {
|
||||
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) {
|
||||
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)
|
||||
trips.sort((a, b) {
|
||||
try {
|
||||
@@ -138,8 +114,6 @@ class TripService {
|
||||
// Obtenir les voyages d'un utilisateur (version simplifiée)
|
||||
Future<List<Trip>> getTripsByUser(String userId) async {
|
||||
try {
|
||||
print('Récupération des voyages pour userId: $userId');
|
||||
|
||||
// Récupérer d'abord les voyages créés par l'utilisateur
|
||||
final QuerySnapshot createdTrips = await _firestore
|
||||
.collection(_tripsCollection)
|
||||
@@ -177,8 +151,6 @@ class TripService {
|
||||
|
||||
// Méthode helper pour convertir un document Firestore en Trip
|
||||
Trip? _convertDocumentToTrip(String docId, Map<String, dynamic> data) {
|
||||
print('\n=== CONVERSION TRIP $docId ===');
|
||||
|
||||
try {
|
||||
// Créer une copie des données pour ne pas modifier l'original
|
||||
Map<String, dynamic> processedData = Map<String, dynamic>.from(data);
|
||||
@@ -211,7 +183,7 @@ class TripService {
|
||||
return trip;
|
||||
|
||||
} 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');
|
||||
return null;
|
||||
}
|
||||
|
||||
24
pubspec.lock
24
pubspec.lock
@@ -25,6 +25,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bloc
|
||||
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.4"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -97,6 +105,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -174,6 +190,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
||||
@@ -35,6 +35,8 @@ dependencies:
|
||||
path_provider: ^2.1.1
|
||||
bcrypt: ^1.1.3
|
||||
location: ^5.0.0
|
||||
flutter_bloc : ^8.1.3
|
||||
equatable: ^2.0.5
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
||||
Reference in New Issue
Block a user