import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:url_launcher/url_launcher.dart'; import 'package:p_tits_pas/services/bug_report_service.dart'; import 'package:go_router/go_router.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); @override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } String? _validateEmail(String? 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; } String? _validatePassword(String? value) { if (value == null || value.isEmpty) { return 'Veuillez entrer votre mot de passe'; } if (value.length < 6) { return 'Le mot de passe doit contenir au moins 6 caractères'; } return null; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, body: LayoutBuilder( builder: (context, constraints) { // Version desktop (web) if (kIsWeb) { final w = constraints.maxWidth; final h = constraints.maxHeight; return FutureBuilder( future: _getImageDimensions(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final imageDimensions = snapshot.data!; final imageHeight = h; final imageWidth = imageHeight * (imageDimensions.width / imageDimensions.height); final remainingWidth = w - imageWidth; final leftMargin = remainingWidth / 4; return Stack( children: [ // Fond en papier Positioned.fill( child: Image.asset( 'assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat, ), ), // Image principale Positioned( left: leftMargin, top: 0, height: imageHeight, width: imageWidth, child: Image.asset( 'assets/images/river_logo_desktop.png', fit: BoxFit.contain, ), ), // Formulaire dans le cadran en bas à droite Positioned( right: 0, bottom: 0, width: w * 0.6, // 60% de la largeur de l'écran height: h * 0.5, // 50% de la hauteur de l'écran child: Padding( padding: EdgeInsets.all(w * 0.02), // 2% de padding child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Labels au-dessus des champs Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( 'Email', style: GoogleFonts.merienda( fontSize: 20, color: Colors.black87, fontWeight: FontWeight.w600, ), ), ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 20), child: Text( 'Mot de passe', style: GoogleFonts.merienda( fontSize: 20, color: Colors.black87, fontWeight: FontWeight.w600, ), ), ), ), ], ), const SizedBox(height: 10), // Champs côte à côte Row( children: [ Expanded( child: _ImageTextField( bg: 'assets/images/field_email.png', width: 400, height: 53, hint: 'Email', controller: _emailController, validator: _validateEmail, ), ), const SizedBox(width: 20), Expanded( child: _ImageTextField( bg: 'assets/images/field_password.png', width: 400, height: 53, hint: 'Mot de passe', obscure: true, controller: _passwordController, validator: _validatePassword, ), ), ], ), const SizedBox(height: 20), // Réduit l'espacement // Bouton centré Center( child: _ImageButton( bg: 'assets/images/btn_green.png', width: 300, height: 40, text: 'Se connecter', textColor: const Color(0xFF2D6A4F), onPressed: () { if (_formKey.currentState?.validate() ?? false) { // TODO: Implémenter la logique de connexion } }, ), ), const SizedBox(height: 10), // Lien mot de passe oublié Center( child: TextButton( onPressed: () { // TODO: Implémenter la logique de récupération de mot de passe }, child: Text( 'Mot de passe oublié ?', style: GoogleFonts.merienda( fontSize: 14, color: const Color(0xFF2D6A4F), decoration: TextDecoration.underline, ), ), ), ), const SizedBox(height: 10), // Lien de création de compte Center( child: Container(), // Suppression du bouton 'Créer un compte' ), const SizedBox(height: 20), // Réduit l'espacement en bas ], ), ), ), ), // Pied de page Positioned( left: 0, right: 0, bottom: 0, child: Container( padding: const EdgeInsets.symmetric(vertical: 8.0), decoration: BoxDecoration( color: Colors.transparent, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _FooterLink( text: 'Contact support', onTap: () async { final Uri emailLaunchUri = Uri( scheme: 'mailto', path: 'support@supernounou.local', ); if (await canLaunchUrl(emailLaunchUri)) { await launchUrl(emailLaunchUri); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Impossible d\'ouvrir le client mail', style: GoogleFonts.merienda(), ), ), ); } }, ), _FooterLink( text: 'Signaler un bug', onTap: () { _showBugReportDialog(context); }, ), _FooterLink( text: 'Mentions légales', onTap: () { Navigator.pushNamed(context, '/legal'); }, ), _FooterLink( text: 'Politique de confidentialité', onTap: () { Navigator.pushNamed(context, '/privacy'); }, ), ], ), ), ), ], ); }, ); } // Version mobile (à implémenter) return const Center( child: Text('Version mobile à implémenter'), ); }, ), ); } void _showBugReportDialog(BuildContext context) { final TextEditingController controller = TextEditingController(); showDialog( context: context, builder: (context) => AlertDialog( title: Text( 'Signaler un bug', style: GoogleFonts.merienda(), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: controller, maxLines: 5, decoration: InputDecoration( hintText: 'Décrivez le problème rencontré...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text( 'Annuler', style: GoogleFonts.merienda(), ), ), TextButton( onPressed: () async { if (controller.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Veuillez décrire le problème', style: GoogleFonts.merienda(), ), ), ); return; } try { await BugReportService.sendReport(controller.text); if (context.mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Rapport envoyé avec succès', style: GoogleFonts.merienda(), ), ), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Erreur lors de l\'envoi du rapport', style: GoogleFonts.merienda(), ), ), ); } } }, child: Text( 'Envoyer', style: GoogleFonts.merienda(), ), ), ], ), ); } Future _getImageDimensions() async { final image = Image.asset('assets/images/river_logo_desktop.png'); final completer = Completer(); image.image.resolve(const ImageConfiguration()).addListener( ImageStreamListener((info, _) { completer.complete(ImageDimensions( width: info.image.width.toDouble(), height: info.image.height.toDouble(), )); }), ); return completer.future; } } class ImageDimensions { final double width; final double height; ImageDimensions({required this.width, required this.height}); } // ─────────────────────────────────────────────────────────────── // Champ texte avec fond image // ─────────────────────────────────────────────────────────────── class _ImageTextField extends StatelessWidget { final String bg; final double width; final double height; final String hint; final bool obscure; final TextEditingController? controller; final String? Function(String?)? validator; const _ImageTextField({ required this.bg, required this.width, required this.height, required this.hint, this.obscure = false, this.controller, this.validator, }); @override Widget build(BuildContext context) { return Container( width: width, height: height, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(bg), fit: BoxFit.fill, ), ), child: TextFormField( controller: controller, obscureText: obscure, textAlign: TextAlign.left, style: GoogleFonts.merienda( fontSize: height * 0.25, color: Colors.black87, ), validator: validator, decoration: InputDecoration( border: InputBorder.none, hintText: hint, hintStyle: GoogleFonts.merienda( fontSize: height * 0.25, color: Colors.black38, ), contentPadding: EdgeInsets.symmetric( horizontal: width * 0.1, vertical: height * 0.3, ), errorStyle: GoogleFonts.merienda( fontSize: height * 0.2, color: Colors.red, ), ), ), ); } } // ─────────────────────────────────────────────────────────────── // Bouton avec fond image // ─────────────────────────────────────────────────────────────── class _ImageButton extends StatelessWidget { final String bg; final double width; final double height; final String text; final Color textColor; final VoidCallback onPressed; const _ImageButton({ required this.bg, required this.width, required this.height, required this.text, required this.textColor, required this.onPressed, }); @override Widget build(BuildContext context) { return Container( width: width, height: height, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(bg), fit: BoxFit.fill, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: onPressed, child: Center( child: Text( text, style: GoogleFonts.merienda( fontSize: height * 0.4, color: textColor, fontWeight: FontWeight.w600, ), ), ), ), ), ); } } // ─────────────────────────────────────────────────────────────── // Lien du pied de page // ─────────────────────────────────────────────────────────────── class _FooterLink extends StatelessWidget { final String text; final VoidCallback onTap; const _FooterLink({ required this.text, required this.onTap, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( text, style: GoogleFonts.merienda( fontSize: 14, color: Colors.black87, decoration: TextDecoration.underline, ), ), ), ); } }