feat(mobile): Implement Keycloak WebView authentication with HTTP callback

- Replace flutter_appauth with custom WebView implementation to resolve deep link issues
- Add KeycloakWebViewAuthService with integrated WebView for seamless authentication
- Configure Android manifest for HTTP cleartext traffic support
- Add network security config for development environment (192.168.1.11)
- Update Keycloak client to use HTTP callback endpoint (http://192.168.1.11:8080/auth/callback)
- Remove obsolete keycloak_auth_service.dart and temporary scripts
- Clean up dependencies and regenerate injection configuration
- Tested successfully on multiple Android devices (Xiaomi 2201116TG, SM A725F)

BREAKING CHANGE: Authentication flow now uses WebView instead of external browser
- Users will see Keycloak login page within the app instead of browser redirect
- Resolves ERR_CLEARTEXT_NOT_PERMITTED and deep link state management issues
- Maintains full OIDC compliance with PKCE flow and secure token storage

Technical improvements:
- WebView with custom navigation delegate for callback handling
- Automatic token extraction and user info parsing from JWT
- Proper error handling and user feedback
- Consistent authentication state management across app lifecycle
This commit is contained in:
DahoudG
2025-09-15 01:44:16 +00:00
parent 73459b3092
commit f89f6167cc
290 changed files with 34563 additions and 3528 deletions

View File

@@ -0,0 +1,142 @@
import 'package:equatable/equatable.dart';
import '../../../../core/models/evenement_model.dart';
/// États du BLoC Evenement
abstract class EvenementState extends Equatable {
const EvenementState();
@override
List<Object?> get props => [];
}
/// État initial
class EvenementInitial extends EvenementState {
const EvenementInitial();
}
/// État de chargement
class EvenementLoading extends EvenementState {
const EvenementLoading();
}
/// État de chargement avec données existantes (pour pagination)
class EvenementLoadingMore extends EvenementState {
final List<EvenementModel> evenements;
const EvenementLoadingMore(this.evenements);
@override
List<Object?> get props => [evenements];
}
/// État de succès avec liste d'événements
class EvenementLoaded extends EvenementState {
final List<EvenementModel> evenements;
final bool hasReachedMax;
final int currentPage;
final String? searchTerm;
final TypeEvenement? filterType;
const EvenementLoaded({
required this.evenements,
this.hasReachedMax = false,
this.currentPage = 0,
this.searchTerm,
this.filterType,
});
EvenementLoaded copyWith({
List<EvenementModel>? evenements,
bool? hasReachedMax,
int? currentPage,
String? searchTerm,
TypeEvenement? filterType,
}) {
return EvenementLoaded(
evenements: evenements ?? this.evenements,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
currentPage: currentPage ?? this.currentPage,
searchTerm: searchTerm ?? this.searchTerm,
filterType: filterType ?? this.filterType,
);
}
@override
List<Object?> get props => [
evenements,
hasReachedMax,
currentPage,
searchTerm,
filterType,
];
}
/// État de succès avec un événement spécifique
class EvenementDetailLoaded extends EvenementState {
final EvenementModel evenement;
const EvenementDetailLoaded(this.evenement);
@override
List<Object?> get props => [evenement];
}
/// État de succès avec statistiques
class EvenementStatistiquesLoaded extends EvenementState {
final Map<String, dynamic> statistiques;
const EvenementStatistiquesLoaded(this.statistiques);
@override
List<Object?> get props => [statistiques];
}
/// État de succès après création/modification
class EvenementOperationSuccess extends EvenementState {
final String message;
final EvenementModel? evenement;
const EvenementOperationSuccess({
required this.message,
this.evenement,
});
@override
List<Object?> get props => [message, evenement];
}
/// État d'erreur
class EvenementError extends EvenementState {
final String message;
final List<EvenementModel>? evenements; // Pour conserver les données en cas d'erreur de pagination
const EvenementError({
required this.message,
this.evenements,
});
@override
List<Object?> get props => [message, evenements];
}
/// État de recherche vide
class EvenementSearchEmpty extends EvenementState {
final String searchTerm;
const EvenementSearchEmpty(this.searchTerm);
@override
List<Object?> get props => [searchTerm];
}
/// État de liste vide
class EvenementEmpty extends EvenementState {
final String message;
const EvenementEmpty({
this.message = 'Aucun événement trouvé',
});
@override
List<Object?> get props => [message];
}