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:
@@ -16,6 +16,7 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
on<LoadMembres>(_onLoadMembres);
|
||||
on<RefreshMembres>(_onRefreshMembres);
|
||||
on<SearchMembres>(_onSearchMembres);
|
||||
on<AdvancedSearchMembres>(_onAdvancedSearchMembres);
|
||||
on<LoadMembreById>(_onLoadMembreById);
|
||||
on<CreateMembre>(_onCreateMembre);
|
||||
on<UpdateMembre>(_onUpdateMembre);
|
||||
@@ -101,6 +102,83 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler pour recherche avancée des membres avec filtres multiples
|
||||
Future<void> _onAdvancedSearchMembres(
|
||||
AdvancedSearchMembres event,
|
||||
Emitter<MembresState> emit,
|
||||
) async {
|
||||
// Si aucun filtre n'est appliqué, recharger tous les membres
|
||||
if (event.filters.isEmpty || _areFiltersEmpty(event.filters)) {
|
||||
add(const LoadMembres());
|
||||
return;
|
||||
}
|
||||
|
||||
emit(const MembresLoading());
|
||||
|
||||
try {
|
||||
final membres = await _membreRepository.advancedSearchMembres(event.filters);
|
||||
emit(MembresLoaded(
|
||||
membres: membres,
|
||||
isSearchResult: true,
|
||||
searchQuery: _buildSearchQueryFromFilters(event.filters),
|
||||
));
|
||||
} catch (e) {
|
||||
final failure = _mapExceptionToFailure(e);
|
||||
emit(MembresError(failure: failure));
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si tous les filtres sont vides
|
||||
bool _areFiltersEmpty(Map<String, dynamic> filters) {
|
||||
return filters.values.every((value) {
|
||||
if (value == null) return true;
|
||||
if (value is String) return value.trim().isEmpty;
|
||||
if (value is List) return value.isEmpty;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/// Construit une chaîne de recherche à partir des filtres pour l'affichage
|
||||
String _buildSearchQueryFromFilters(Map<String, dynamic> filters) {
|
||||
final activeFilters = <String>[];
|
||||
|
||||
filters.forEach((key, value) {
|
||||
if (value != null && value.toString().isNotEmpty) {
|
||||
switch (key) {
|
||||
case 'nom':
|
||||
activeFilters.add('Nom: $value');
|
||||
break;
|
||||
case 'prenom':
|
||||
activeFilters.add('Prénom: $value');
|
||||
break;
|
||||
case 'email':
|
||||
activeFilters.add('Email: $value');
|
||||
break;
|
||||
case 'telephone':
|
||||
activeFilters.add('Téléphone: $value');
|
||||
break;
|
||||
case 'actif':
|
||||
activeFilters.add('Statut: ${value == true ? "Actif" : "Inactif"}');
|
||||
break;
|
||||
case 'profession':
|
||||
activeFilters.add('Profession: $value');
|
||||
break;
|
||||
case 'ville':
|
||||
activeFilters.add('Ville: $value');
|
||||
break;
|
||||
case 'ageMin':
|
||||
activeFilters.add('Âge min: $value');
|
||||
break;
|
||||
case 'ageMax':
|
||||
activeFilters.add('Âge max: $value');
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return activeFilters.join(', ');
|
||||
}
|
||||
|
||||
/// Handler pour charger un membre par ID
|
||||
Future<void> _onLoadMembreById(
|
||||
LoadMembreById event,
|
||||
|
||||
Reference in New Issue
Block a user