Files
unionflow-server-api/unionflow-mobile-apps/lib/features/evenements/presentation/widgets/evenement_search_bar.dart
DahoudG f89f6167cc 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
2025-09-15 01:44:16 +00:00

82 lines
2.1 KiB
Dart

import 'package:flutter/material.dart';
import 'dart:async';
/// Barre de recherche pour les événements
class EvenementSearchBar extends StatefulWidget {
final Function(String) onSearch;
final String? initialValue;
final String hintText;
final Duration debounceTime;
const EvenementSearchBar({
super.key,
required this.onSearch,
this.initialValue,
this.hintText = 'Rechercher un événement...',
this.debounceTime = const Duration(milliseconds: 500),
});
@override
State<EvenementSearchBar> createState() => _EvenementSearchBarState();
}
class _EvenementSearchBarState extends State<EvenementSearchBar> {
late TextEditingController _controller;
Timer? _debounceTimer;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.initialValue);
}
@override
void dispose() {
_controller.dispose();
_debounceTimer?.cancel();
super.dispose();
}
void _onSearchChanged(String value) {
_debounceTimer?.cancel();
_debounceTimer = Timer(widget.debounceTime, () {
widget.onSearch(value.trim());
});
}
void _clearSearch() {
_controller.clear();
widget.onSearch('');
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: TextField(
controller: _controller,
onChanged: _onSearchChanged,
decoration: InputDecoration(
hintText: widget.hintText,
prefixIcon: const Icon(Icons.search, color: Colors.grey),
suffixIcon: _controller.text.isNotEmpty
? IconButton(
onPressed: _clearSearch,
icon: const Icon(Icons.clear, color: Colors.grey),
)
: null,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
),
);
}
}