- 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
82 lines
2.1 KiB
Dart
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,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|