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:
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user