Amélioration de la splash_page et du login

This commit is contained in:
d6soft
2025-06-04 16:51:40 +02:00
parent 8c9e9a21c4
commit bcfdbb2c8b
168 changed files with 153842 additions and 6183 deletions

View File

@@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:convert';
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode;
import 'dart:js' as js;
import 'package:go_router/go_router.dart';
import 'package:go_router/src/state.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:geosector_app/core/repositories/user_repository.dart';
import 'package:http/http.dart' as http;
import 'package:geosector_app/core/services/app_info_service.dart';
import 'package:geosector_app/presentation/widgets/custom_button.dart';
import 'package:geosector_app/presentation/widgets/custom_text_field.dart';
import 'package:geosector_app/core/services/location_service.dart';
import 'package:geosector_app/core/services/connectivity_service.dart';
import 'package:geosector_app/presentation/widgets/connectivity_indicator.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
class LoginPage extends StatefulWidget {
@@ -51,6 +52,7 @@ class _LoginPageState extends State<LoginPage> {
final _passwordController = TextEditingController();
final _usernameFocusNode = FocusNode();
bool _obscurePassword = true;
String _appVersion = '';
// Type de connexion (utilisateur ou administrateur)
late String _loginType;
@@ -63,6 +65,37 @@ class _LoginPageState extends State<LoginPage> {
// État de la connexion Internet
bool _isConnected = false;
Future<void> _getAppVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
if (mounted) {
setState(() {
_appVersion = packageInfo.version;
});
}
} catch (e) {
debugPrint('Erreur lors de la récupération de la version: $e');
// Fallback sur la version du AppInfoService si elle existe
if (mounted) {
setState(() {
_appVersion = AppInfoService.fullVersion
.split(' ')
.last; // Extraire juste le numéro
});
}
}
}
Future<void> _checkConnectivity() async {
await connectivityService.checkConnectivity();
if (mounted) {
setState(() {
_isConnected = connectivityService.isConnected;
});
}
}
@override
void initState() {
super.initState();
@@ -163,6 +196,12 @@ class _LoginPageState extends State<LoginPage> {
}
});
// Récupérer la version de l'application
_getAppVersion();
// Vérification de connectivité au démarrage
_checkConnectivity();
// Pré-remplir le champ username avec l'identifiant du dernier utilisateur connecté
// seulement si le rôle correspond au type de login
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -538,6 +577,14 @@ class _LoginPageState extends State<LoginPage> {
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
AppInfoService.fullVersion,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.primary.withOpacity(0.7),
),
),
const SizedBox(height: 8),
// Ajouter un texte de débogage uniquement en mode développement
if (kDebugMode)
Text(
@@ -558,7 +605,38 @@ class _LoginPageState extends State<LoginPage> {
const SizedBox(height: 16),
// Indicateur de connectivité
ConnectivityIndicator(),
const ConnectivityIndicator(),
const SizedBox(height: 16),
if (!kIsWeb && !_isConnected)
Container(
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.colorScheme.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color:
theme.colorScheme.error.withOpacity(0.3),
),
),
child: Column(
children: [
Icon(Icons.signal_wifi_off,
color: theme.colorScheme.error, size: 32),
const SizedBox(height: 8),
Text('Connexion Internet requise',
style: theme.textTheme.titleMedium
?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.error)),
const SizedBox(height: 8),
const Text(
'Veuillez vous connecter à Internet (WiFi ou données mobiles) pour pouvoir vous connecter.'),
],
),
),
const SizedBox(height: 16),
// Formulaire de connexion
@@ -689,7 +767,7 @@ class _LoginPageState extends State<LoginPage> {
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
// Naviguer vers la page de récupération de mot de passe
_showForgotPasswordDialog(context);
},
child: Text(
'Mot de passe oublié ?',
@@ -863,10 +941,10 @@ class _LoginPageState extends State<LoginPage> {
onPressed: () {
context.go('/register');
},
child: Text(
child: const Text(
'Inscription Administrateur',
style: TextStyle(
color: theme.colorScheme.tertiary,
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
@@ -897,8 +975,255 @@ class _LoginPageState extends State<LoginPage> {
),
),
),
// Badge de version en bas à droite
if (_appVersion.isNotEmpty)
Positioned(
bottom: 16,
right: 16,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: theme.colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: theme.colorScheme.primary.withOpacity(0.3),
width: 1,
),
),
child: Text(
'v$_appVersion',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.primary.withOpacity(0.8),
fontSize: 10,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
);
}
// Affiche la boîte de dialogue pour la récupération de mot de passe
void _showForgotPasswordDialog(BuildContext context) {
final emailController = TextEditingController();
final formKey = GlobalKey<FormState>();
bool isLoading = false;
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: const Row(
children: [
Icon(Icons.lock_reset, color: Colors.blue),
SizedBox(width: 10),
Text('Récupération de mot de passe'),
],
),
content: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Veuillez entrer votre adresse email pour recevoir un nouveau mot de passe.',
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 16),
CustomTextField(
controller: emailController,
label: 'Email',
hintText: 'Entrez votre email',
prefixIcon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre email';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(value)) {
return 'Veuillez entrer un email valide';
}
return null;
},
),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: isLoading
? null
: () async {
if (formKey.currentState!.validate()) {
setState(() {
isLoading = true;
});
try {
// Vérifier la connexion Internet
await connectivityService.checkConnectivity();
if (!connectivityService.isConnected) {
throw Exception('Aucune connexion Internet');
}
// Construire l'URL de l'API
final baseUrl = Uri.base.origin;
final apiUrl = '$baseUrl/api/lostpassword';
print('Envoi de la requête à: $apiUrl');
print('Email: ${emailController.text.trim()}');
http.Response? response;
try {
// Envoyer la requête à l'API
response = await http.post(
Uri.parse(apiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'email': emailController.text.trim(),
}),
);
print('Réponse reçue: ${response.statusCode}');
print('Corps de la réponse: ${response.body}');
// Si la réponse est 404, c'est peut-être un problème de route
if (response.statusCode == 404) {
// Essayer avec une URL alternative
final alternativeUrl =
'$baseUrl/api/index.php/lostpassword';
print(
'Tentative avec URL alternative: $alternativeUrl');
final alternativeResponse = await http.post(
Uri.parse(alternativeUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'email': emailController.text.trim(),
}),
);
print(
'Réponse alternative reçue: ${alternativeResponse.statusCode}');
print(
'Corps de la réponse alternative: ${alternativeResponse.body}');
// Si la réponse alternative est un succès, utiliser cette réponse
if (alternativeResponse.statusCode == 200) {
response = alternativeResponse;
}
}
} catch (e) {
print(
'Erreur lors de l\'envoi de la requête: $e');
throw Exception('Erreur de connexion: $e');
}
// Traiter la réponse
if (response != null &&
response.statusCode == 200) {
// Modifier le contenu de la boîte de dialogue pour afficher le message de succès
setState(() {
isLoading = false;
});
// Remplacer le contenu de la boîte de dialogue par un message de succès
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
// Fermer automatiquement la boîte de dialogue après 2 secondes
Future.delayed(Duration(seconds: 2), () {
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
});
return const AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
color: Colors.green,
size: 48,
),
SizedBox(height: 16),
Text(
'Vous recevrez un nouveau mot de passe par email',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
],
),
);
},
);
} else {
// Fermer la boîte de dialogue actuelle
Navigator.of(context).pop();
// Afficher un message d'erreur
final responseData = json.decode(response.body);
throw Exception(responseData['message'] ??
'Erreur lors de la récupération du mot de passe');
}
} catch (e) {
// Afficher un message d'erreur
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e
.toString()
.contains('Exception:')
? e.toString().split('Exception: ')[1]
: 'Erreur lors de la récupération du mot de passe'),
backgroundColor: Colors.red,
),
);
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
},
child: isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text('Recevoir un nouveau mot de passe'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
],
);
});
},
);
}
}