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

@@ -5,6 +5,15 @@ import '../../../../shared/theme/app_theme.dart';
import '../bloc/membres_bloc.dart';
import '../bloc/membres_event.dart';
import '../bloc/membres_state.dart';
import '../widgets/dashboard/welcome_section_widget.dart';
import '../widgets/dashboard/members_kpi_section_widget.dart';
import '../widgets/dashboard/members_quick_actions_widget.dart';
import '../widgets/dashboard/members_analytics_widget.dart';
import '../widgets/dashboard/members_enhanced_list_widget.dart';
import '../widgets/dashboard/members_recent_activities_widget.dart';
import '../widgets/dashboard/members_advanced_filters_widget.dart';
import '../widgets/dashboard/members_smart_search_widget.dart';
import '../widgets/dashboard/members_notifications_widget.dart';
class MembresDashboardPage extends StatefulWidget {
const MembresDashboardPage({super.key});
@@ -15,6 +24,8 @@ class MembresDashboardPage extends StatefulWidget {
class _MembresDashboardPageState extends State<MembresDashboardPage> {
late MembresBloc _membresBloc;
Map<String, dynamic> _currentFilters = {};
String _currentSearchQuery = '';
@override
void initState() {
@@ -27,6 +38,37 @@ class _MembresDashboardPageState extends State<MembresDashboardPage> {
_membresBloc.add(const LoadMembres());
}
void _onFiltersChanged(Map<String, dynamic> filters) {
setState(() {
_currentFilters = filters;
});
// TODO: Appliquer les filtres aux données
_loadData();
}
void _onSearchChanged(String query) {
setState(() {
_currentSearchQuery = query;
});
// TODO: Appliquer la recherche
if (query.isNotEmpty) {
_membresBloc.add(SearchMembres(query));
} else {
_loadData();
}
}
void _onSuggestionSelected(Map<String, dynamic> suggestion) {
switch (suggestion['type']) {
case 'quick_filter':
_onFiltersChanged(suggestion['filter']);
break;
case 'member':
// TODO: Naviguer vers les détails du membre
break;
}
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
@@ -117,37 +159,109 @@ class _MembresDashboardPageState extends State<MembresDashboardPage> {
}
Widget _buildDashboard() {
return Container(
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.dashboard,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'Dashboard Vide',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.grey,
),
),
SizedBox(height: 8),
Text(
'Prêt à être reconstruit pièce par pièce',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section d'accueil
const MembersWelcomeSectionWidget(),
const SizedBox(height: 24),
// Notifications en temps réel
const MembersNotificationsWidget(),
// Recherche intelligente
MembersSmartSearchWidget(
onSearch: _onSearchChanged,
onSuggestionSelected: _onSuggestionSelected,
recentSearches: const [], // TODO: Implémenter l'historique
),
const SizedBox(height: 16),
// Filtres avancés
MembersAdvancedFiltersWidget(
onFiltersChanged: _onFiltersChanged,
initialFilters: _currentFilters,
),
// KPI Cards
const MembersKPISectionWidget(),
const SizedBox(height: 24),
// Actions rapides
const MembersQuickActionsWidget(),
const SizedBox(height: 24),
// Graphiques et analyses
const MembersAnalyticsWidget(),
const SizedBox(height: 24),
// Activités récentes
const MembersRecentActivitiesWidget(),
const SizedBox(height: 24),
// Liste des membres améliorée
BlocBuilder<MembresBloc, MembresState>(
builder: (context, state) {
if (state is MembresLoaded) {
return MembersEnhancedListWidget(
members: state.membres,
onMemberTap: (member) {
// TODO: Naviguer vers les détails du membre
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Détails de ${member.nomComplet}'),
backgroundColor: AppTheme.primaryColor,
),
);
},
onMemberCall: (member) {
// TODO: Appeler le membre
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Appel de ${member.nomComplet}'),
backgroundColor: AppTheme.successColor,
),
);
},
onMemberMessage: (member) {
// TODO: Envoyer un message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Message à ${member.nomComplet}'),
backgroundColor: AppTheme.infoColor,
),
);
},
onMemberEdit: (member) {
// TODO: Modifier le membre
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Modification de ${member.nomComplet}'),
backgroundColor: AppTheme.warningColor,
),
);
},
searchQuery: _currentSearchQuery,
filters: _currentFilters,
);
} else if (state is MembresLoading) {
return MembersEnhancedListWidget(
members: const [],
onMemberTap: (member) {},
isLoading: true,
searchQuery: '',
filters: const {},
);
} else {
return const Center(
child: Text('Erreur lors du chargement des membres'),
);
}
},
),
],
),
);
}