From 14775dc90d7fcfd39916bc56cde1c82c1ac4b845 Mon Sep 17 00:00:00 2001 From: Abraham Date: Tue, 15 Jul 2025 23:02:46 -0700 Subject: [PATCH] login implementado --- lib/pages/login_page/login_page.dart | 499 +++++++++++---- lib/pages/login_page/widgets/login_form.dart | 606 +++++++++++++------ 2 files changed, 802 insertions(+), 303 deletions(-) diff --git a/lib/pages/login_page/login_page.dart b/lib/pages/login_page/login_page.dart index c38fc7b..38cdc87 100644 --- a/lib/pages/login_page/login_page.dart +++ b/lib/pages/login_page/login_page.dart @@ -15,8 +15,51 @@ class LoginPage extends StatefulWidget { State createState() => _LoginPageState(); } -class _LoginPageState extends State { +class _LoginPageState extends State with TickerProviderStateMixin { final scaffoldKey = GlobalKey(); + late AnimationController _fadeController; + late AnimationController _slideController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + @override + void initState() { + super.initState(); + _fadeController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + _slideController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _fadeController, + curve: Curves.easeInOut, + )); + + _slideAnimation = Tween( + begin: const Offset(-0.3, 0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _slideController, + curve: Curves.elasticOut, + )); + + _fadeController.forward(); + _slideController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -27,158 +70,330 @@ class _LoginPageState extends State { child: LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth < 768) { - // Vista móvil - fondo degradado completo + // Vista móvil - fondo degradado completo con mejoras return Container( width: double.infinity, height: double.infinity, decoration: const BoxDecoration( gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, + begin: Alignment.topLeft, + end: Alignment.bottomRight, colors: [ - Color(0xFF22C55E), // Verde izquierda - Color(0xFF3B82F6), // Azul centro - Color(0xFF8B5CF6), // Morado derecha + Color( + 0xFF0F172A), // Azul muy oscuro (más oscuro para móvil) + Color(0xFF1E293B), // Azul oscuro + Color(0xFF075985), // Azul medio oscuro + Color(0xFF059669), // Verde más oscuro ], + stops: [0.0, 0.3, 0.7, 1.0], ), ), - child: SingleChildScrollView( - child: Container( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context).size.height, + child: Stack( + children: [ + // Efectos de partículas/círculos flotantes (más sutiles en móvil) + ...List.generate( + 4, (index) => _buildFloatingCircle(index, constraints)), + // Overlay oscuro con blur para mejor legibilidad + Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + ), ), - padding: const EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Logo NetHive para móvil - Container( - margin: const EdgeInsets.only(bottom: 30), + SafeArea( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height - + MediaQuery.of(context).padding.top - + MediaQuery.of(context).padding.bottom, + ), + padding: const EdgeInsets.symmetric( + horizontal: 24, // Padding simétrico + vertical: 20, + ), child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment + .center, // Centrado horizontal children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Image.asset( - 'assets/images/favicon.png', - fit: BoxFit.contain, + // Logo NetHive para móvil + FadeTransition( + opacity: _fadeAnimation, + child: Container( + margin: const EdgeInsets.only(bottom: 40), + child: Column( + children: [ + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: + Colors.black.withOpacity(0.3), + blurRadius: 30, + offset: const Offset(0, 15), + ), + BoxShadow( + color: + Colors.white.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, -5), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Image.asset( + 'assets/images/favicon.png', + fit: BoxFit.contain, + ), + ), + ), + const SizedBox(height: 24), + const Text( + 'NetHive', + style: TextStyle( + fontSize: 38, + fontWeight: FontWeight.bold, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(0, 3), + blurRadius: 15, + color: Colors.black38, + ), + ], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + const Text( + 'Infrastructure Management', + style: TextStyle( + fontSize: 16, + color: Colors.white70, + fontWeight: FontWeight.w300, + letterSpacing: 0.8, + ), + textAlign: TextAlign.center, + ), + ], ), ), ), - const SizedBox(height: 16), - const Text( - 'NetHive', - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const Text( - 'Infrastructure Management', - style: TextStyle( - fontSize: 16, - color: Colors.white70, + // Formulario con contenedor para mejor contraste + SlideTransition( + position: _slideAnimation, + child: Container( + width: double.infinity, + constraints: + const BoxConstraints(maxWidth: 400), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.1), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 30, + offset: const Offset(0, 15), + ), + ], + ), + child: const LoginForm(), ), ), + const SizedBox(height: 20), ], ), ), - const LoginForm(), - ], + ), ), - ), + ], ), ); } else { // Vista desktop - columna izquierda sólida, derecha degradado return Row( children: [ - // Lado izquierdo - Formulario (fondo sólido) + // Lado izquierdo - Formulario (fondo sólido con efectos) Expanded( flex: 1, child: Container( - color: - const Color(0xFF1E293B), // Fondo sólido azul oscuro - padding: const EdgeInsets.symmetric( - horizontal: 60, vertical: 40), - child: Center( - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 400), - child: const LoginForm(), + decoration: BoxDecoration( + color: const Color(0xFF0F172A), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 30, + offset: const Offset(10, 0), ), - ), + ], + ), + child: Stack( + children: [ + // Patrón de puntos sutil + Positioned.fill( + child: CustomPaint( + painter: DotPatternPainter(), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 60, vertical: 40), + child: Center( + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: 420), + child: SlideTransition( + position: _slideAnimation, + child: const LoginForm(), + ), + ), + ), + ), + ), + ], ), ), ), - // Lado derecho - Logo (con degradado) + // Lado derecho - Logo (con degradado mejorado) Expanded( flex: 1, child: Container( decoration: const BoxDecoration( gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, + begin: Alignment.topLeft, + end: Alignment.bottomRight, colors: [ - Color(0xFF22C55E), // Verde izquierda - Color(0xFF3B82F6), // Azul centro - Color(0xFF8B5CF6), // Morado derecha + Color(0xFF1E40AF), // Azul profundo + Color(0xFF3B82F6), // Azul brillante + Color(0xFF10B981), // Verde esmeralda + Color(0xFF059669), // Verde intenso + Color(0xFF7C3AED), // Púrpura ], + stops: [0.0, 0.25, 0.5, 0.75, 1.0], ), ), - padding: const EdgeInsets.all(60), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + child: Stack( children: [ - // Logo NetHive - Container( - width: 120, - height: 120, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 10), - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(24), - child: Image.asset( - 'assets/images/favicon.png', - fit: BoxFit.contain, + // Efectos de círculos flotantes + ...List.generate( + 8, + (index) => _buildFloatingCircle( + index, constraints, true)), + // Contenido centrado horizontal y verticalmente + Center( + child: Container( + padding: const EdgeInsets.all(60), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + // Logo NetHive con animación + FadeTransition( + opacity: _fadeAnimation, + child: Container( + width: 150, + height: 150, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: + Colors.black.withOpacity(0.2), + blurRadius: 40, + offset: const Offset(0, 20), + ), + BoxShadow( + color: + Colors.white.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, -10), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(30), + child: Image.asset( + 'assets/images/favicon.png', + fit: BoxFit.contain, + ), + ), + ), + ), + const SizedBox(height: 40), + FadeTransition( + opacity: _fadeAnimation, + child: const Text( + 'NetHive', + style: TextStyle( + fontSize: 52, + fontWeight: FontWeight.bold, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(0, 4), + blurRadius: 20, + color: Colors.black26, + ), + ], + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 12), + FadeTransition( + opacity: _fadeAnimation, + child: Text( + 'Infrastructure Management', + style: TextStyle( + fontSize: 20, + color: Colors.white.withOpacity(0.8), + fontWeight: FontWeight.w300, + letterSpacing: 1.2, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + // Línea decorativa + FadeTransition( + opacity: _fadeAnimation, + child: Container( + width: 100, + height: 2, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Colors.transparent, + Colors.white, + Colors.transparent, + ], + ), + borderRadius: BorderRadius.circular(2), + ), + ), + ), + ], ), ), ), - const SizedBox(height: 32), - const Text( - 'NetHive', - style: TextStyle( - fontSize: 48, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 8), - const Text( - 'Infrastructure Management', - style: TextStyle( - fontSize: 18, - color: Colors.white70, - fontWeight: FontWeight.w300, - ), - ), ], ), ), @@ -191,4 +406,76 @@ class _LoginPageState extends State { ), ); } + + Widget _buildFloatingCircle(int index, BoxConstraints constraints, + [bool isRightSide = false]) { + final random = [ + {'size': 80.0, 'top': 50.0, 'left': 30.0, 'opacity': 0.1}, + {'size': 120.0, 'top': 200.0, 'right': 50.0, 'opacity': 0.08}, + {'size': 60.0, 'bottom': 150.0, 'left': 80.0, 'opacity': 0.12}, + {'size': 100.0, 'top': 300.0, 'left': 200.0, 'opacity': 0.06}, + {'size': 140.0, 'bottom': 100.0, 'right': 100.0, 'opacity': 0.09}, + {'size': 70.0, 'top': 450.0, 'right': 150.0, 'opacity': 0.11}, + {'size': 90.0, 'top': 100.0, 'left': 150.0, 'opacity': 0.07}, + {'size': 110.0, 'bottom': 200.0, 'left': 50.0, 'opacity': 0.08}, + ]; + + if (index >= random.length) return const SizedBox(); + + final circle = random[index]; + + return AnimatedBuilder( + animation: _fadeController, + builder: (context, child) { + return Positioned( + top: circle['top'] as double?, + bottom: circle['bottom'] as double?, + left: circle['left'] as double?, + right: circle['right'] as double?, + child: Transform.scale( + scale: 0.5 + (_fadeAnimation.value * 0.5), + child: Container( + width: circle['size'] as double, + height: circle['size'] as double, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity( + (circle['opacity'] as double) * _fadeAnimation.value), + border: Border.all( + color: Colors.white.withOpacity(0.1 * _fadeAnimation.value), + width: 1, + ), + ), + ), + ), + ); + }, + ); + } +} + +// Custom painter para patrón de puntos +class DotPatternPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.white.withOpacity(0.02) + ..style = PaintingStyle.fill; + + const double spacing = 30; + const double dotSize = 1.5; + + for (double x = 0; x < size.width; x += spacing) { + for (double y = 0; y < size.height; y += spacing) { + canvas.drawCircle( + Offset(x, y), + dotSize, + paint, + ); + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } diff --git a/lib/pages/login_page/widgets/login_form.dart b/lib/pages/login_page/widgets/login_form.dart index 5b2adbf..b10c2b5 100644 --- a/lib/pages/login_page/widgets/login_form.dart +++ b/lib/pages/login_page/widgets/login_form.dart @@ -20,15 +20,44 @@ class LoginForm extends StatefulWidget { State createState() => _LoginFormState(); } -class _LoginFormState extends State { +class _LoginFormState extends State with TickerProviderStateMixin { final formKey = GlobalKey(); bool passwordVisibility = false; + late AnimationController _buttonController; + late Animation _buttonScale; + bool _isLoading = false; + + @override + void initState() { + super.initState(); + _buttonController = AnimationController( + duration: const Duration(milliseconds: 150), + vsync: this, + ); + _buttonScale = Tween( + begin: 1.0, + end: 0.95, + ).animate(CurvedAnimation( + parent: _buttonController, + curve: Curves.easeInOut, + )); + } + + @override + void dispose() { + _buttonController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { final UserState userState = Provider.of(context); + final isMobile = MediaQuery.of(context).size.width < 768; Future login() async { + setState(() => _isLoading = true); + _buttonController.forward().then((_) => _buttonController.reverse()); + //Login try { // Check if user exists @@ -37,6 +66,7 @@ class _LoginFormState extends State { if (userId == null) { await ApiErrorHandler.callToast('Este Correo no está registrado'); + setState(() => _isLoading = false); return; } @@ -57,6 +87,7 @@ class _LoginFormState extends State { if (supabase.auth.currentUser == null) { await ApiErrorHandler.callToast(); + setState(() => _isLoading = false); return; } @@ -64,6 +95,7 @@ class _LoginFormState extends State { if (currentUser == null) { await ApiErrorHandler.callToast(); + setState(() => _isLoading = false); return; } /* @@ -91,16 +123,17 @@ class _LoginFormState extends State { userState.emailController.text, ); await ApiErrorHandler.callToast('Credenciales Invalidas'); - + setState(() => _isLoading = false); return; } log('Error al iniciar sesion - $e'); + setState(() => _isLoading = false); } } return Container( width: double.infinity, - constraints: const BoxConstraints(maxWidth: 400), + constraints: const BoxConstraints(maxWidth: 420), child: Form( key: formKey, child: Column( @@ -109,45 +142,75 @@ class _LoginFormState extends State { // Logo NetHive para formulario (solo en desktop) MediaQuery.of(context).size.width >= 768 ? Container( - margin: const EdgeInsets.only(bottom: 40), - child: Row( + margin: const EdgeInsets.only(bottom: 50), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: const Color(0xFF3B82F6), - borderRadius: BorderRadius.circular(8), - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Image.asset( - 'assets/images/favicon.png', - fit: BoxFit.contain, - ), - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + Row( children: [ - Text( - 'Bienvenido a NetHive', - style: GoogleFonts.inter( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Color(0xFF3B82F6), + Color(0xFF10B981) + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: const Color(0xFF3B82F6) + .withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: Image.asset( + 'assets/images/favicon.png', + fit: BoxFit.contain, + ), ), ), - Text( - 'Plataforma de Gestión de Infraestructura', - style: GoogleFonts.inter( - fontSize: 14, - color: Colors.white70, - ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bienvenido a NetHive', + style: GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + Text( + 'Plataforma de Gestión de Infraestructura', + style: GoogleFonts.inter( + fontSize: 14, + color: Colors.white60, + fontWeight: FontWeight.w300, + ), + ), + ], ), ], ), + const SizedBox(height: 20), + Container( + width: 60, + height: 3, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF10B981), Color(0xFF3B82F6)], + ), + borderRadius: BorderRadius.circular(2), + ), + ), ], ), ) @@ -157,163 +220,184 @@ class _LoginFormState extends State { Text( 'CORREO ELECTRÓNICO', style: GoogleFonts.inter( - fontSize: 12, + fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white, - letterSpacing: 1.2, + letterSpacing: 1.5, ), ), - const SizedBox(height: 8), + const SizedBox(height: 12), - // Campo de email - TextFormField( - controller: userState.emailController, - onFieldSubmitted: (value) async { - if (!formKey.currentState!.validate()) { - return; - } - await login(); - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'El correo es requerido'; - } else if (!EmailValidator.validate(value)) { - return 'Favor de ingresar un correo valido'; - } - return null; - }, - style: GoogleFonts.inter( - color: Colors.white, - fontSize: 16, - ), - decoration: InputDecoration( - hintText: 'admin@nethive.com', - hintStyle: GoogleFonts.inter( - color: Colors.white54, - fontSize: 16, - ), - filled: true, - fillColor: Colors.white.withOpacity(0.1), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - ), - ), - - const SizedBox(height: 20), - - // Título contraseña - Text( - 'CONTRASEÑA', - style: GoogleFonts.inter( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: 1.2, - ), - ), - const SizedBox(height: 8), - - // Campo de contraseña - TextFormField( - controller: userState.passwordController, - obscureText: !passwordVisibility, - onFieldSubmitted: (value) async { - if (!formKey.currentState!.validate()) { - return; - } - await login(); - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'La contraseña es requerida'; - } - return null; - }, - style: GoogleFonts.inter( - color: Colors.white, - fontSize: 16, - ), - decoration: InputDecoration( - hintText: '••••••', - hintStyle: GoogleFonts.inter( - color: Colors.white54, - fontSize: 16, - ), - filled: true, - fillColor: Colors.white.withOpacity(0.1), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - suffixIcon: IconButton( - icon: Icon( - passwordVisibility - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.white54, + // Campo de email con efectos mejorados + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, 8), ), - onPressed: () => setState( - () => passwordVisibility = !passwordVisibility, - ), - ), + ], ), - ), - - const SizedBox(height: 32), - - // Botón de iniciar sesión - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: () async { + child: TextFormField( + controller: userState.emailController, + onFieldSubmitted: (value) async { if (!formKey.currentState!.validate()) { return; } await login(); }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF22C55E), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - elevation: 0, + validator: (value) { + if (value == null || value.isEmpty) { + return 'El correo es requerido'; + } else if (!EmailValidator.validate(value)) { + return 'Favor de ingresar un correo valido'; + } + return null; + }, + style: GoogleFonts.inter( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w400, ), - child: Text( - 'INICIAR SESIÓN', - style: GoogleFonts.inter( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: 1.2, + decoration: InputDecoration( + hintText: 'admin@nethive.com', + hintStyle: GoogleFonts.inter( + color: isMobile + ? Colors.white.withOpacity(0.6) + : Colors.white.withOpacity(0.4), + fontSize: 16, + ), + filled: true, + fillColor: isMobile + ? Colors.white.withOpacity(0.15) // Más opaco en móvil + : Colors.white.withOpacity(0.08), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Color(0xFF10B981), + width: 2, + ), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 18, + ), + prefixIcon: Container( + padding: const EdgeInsets.all(12), + child: Icon( + Icons.email_outlined, + color: isMobile + ? Colors.white.withOpacity(0.8) + : Colors.white60, + size: 20, + ), ), ), ), ), + const SizedBox(height: 24), + + // Título contraseña + Text( + 'CONTRASEÑA', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 1.5, + ), + ), const SizedBox(height: 12), - // Enlace "Conexión insegura" - Center( - child: TextButton( - onPressed: () {}, - child: Text( - 'Conexión insegura', - style: GoogleFonts.inter( - color: const Color(0xFFEF4444), - fontSize: 13, - decoration: TextDecoration.underline, - decorationColor: const Color(0xFFEF4444), + // Campo de contraseña con efectos mejorados + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: TextFormField( + controller: userState.passwordController, + obscureText: !passwordVisibility, + onFieldSubmitted: (value) async { + if (!formKey.currentState!.validate()) { + return; + } + await login(); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'La contraseña es requerida'; + } + return null; + }, + style: GoogleFonts.inter( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w400, + ), + decoration: InputDecoration( + hintText: '••••••••', + hintStyle: GoogleFonts.inter( + color: isMobile + ? Colors.white.withOpacity(0.6) + : Colors.white.withOpacity(0.4), + fontSize: 16, + ), + filled: true, + fillColor: isMobile + ? Colors.white.withOpacity(0.15) // Más opaco en móvil + : Colors.white.withOpacity(0.08), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Color(0xFF10B981), + width: 2, + ), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 18, + ), + prefixIcon: Container( + padding: const EdgeInsets.all(12), + child: Icon( + Icons.lock_outline, + color: isMobile + ? Colors.white.withOpacity(0.8) + : Colors.white60, + size: 20, + ), + ), + suffixIcon: IconButton( + icon: Icon( + passwordVisibility + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: isMobile + ? Colors.white.withOpacity(0.8) + : Colors.white60, + size: 20, + ), + onPressed: () => setState( + () => passwordVisibility = !passwordVisibility, + ), ), ), ), @@ -321,27 +405,149 @@ class _LoginFormState extends State { const SizedBox(height: 40), - // Características principales - Text( - 'CARACTERÍSTICAS PRINCIPALES', - style: GoogleFonts.inter( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: 1.2, + // Botón de iniciar sesión mejorado + ScaleTransition( + scale: _buttonScale, + child: Container( + width: double.infinity, + height: 56, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: isMobile + ? const Color(0xFF0369A1) + .withOpacity(0.6) // Azul en móvil + : const Color(0xFF10B981) + .withOpacity(0.4), // Verde en desktop + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: ElevatedButton( + onPressed: _isLoading + ? null + : () async { + if (!formKey.currentState!.validate()) { + return; + } + await login(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: isMobile + ? const Color( + 0xFF0369A1) // Azul más contrastante en móvil + : const Color(0xFF10B981), // Verde en desktop + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: _isLoading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : Text( + 'INICIAR SESIÓN', + style: GoogleFonts.inter( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 1.2, + ), + ), + ), ), ), + const SizedBox(height: 16), - // Lista de características - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildFeatureItem('Gestión completa de infraestructura'), - _buildFeatureItem('Monitoreo en tiempo real'), - _buildFeatureItem('Reportes avanzados'), - _buildFeatureItem('Dashboard intuitivo'), - ], + // Enlace "Conexión segura" mejorado + Center( + child: TextButton( + onPressed: () {}, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.lock_outline, + color: Color(0xFF10B981), + size: 16, + ), + const SizedBox(width: 8), + Text( + 'Conexión segura', + style: GoogleFonts.inter( + color: const Color(0xFF10B981), + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 50), + + // Características principales con diseño mejorado + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.03), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.white.withOpacity(0.1), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 6, + height: 6, + decoration: const BoxDecoration( + color: Color(0xFF10B981), + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Text( + 'CARACTERÍSTICAS PRINCIPALES', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 1.5, + ), + ), + ], + ), + const SizedBox(height: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildFeatureItem('Gestión completa de infraestructura', + Icons.dashboard_outlined), + _buildFeatureItem( + 'Monitoreo en tiempo real', Icons.analytics_outlined), + _buildFeatureItem( + 'Reportes avanzados', Icons.assessment_outlined), + _buildFeatureItem( + 'Dashboard intuitivo', Icons.widgets_outlined), + ], + ), + ], + ), ), ], ), @@ -349,21 +555,26 @@ class _LoginFormState extends State { ); } - Widget _buildFeatureItem(String text) { + Widget _buildFeatureItem(String text, IconData icon) { return Padding( - padding: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.only(bottom: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.only(top: 6, right: 12), - width: 6, - height: 6, - decoration: const BoxDecoration( - color: Color(0xFF22C55E), - shape: BoxShape.circle, + width: 32, + height: 32, + decoration: BoxDecoration( + color: const Color(0xFF10B981).withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + color: const Color(0xFF10B981), + size: 16, ), ), + const SizedBox(width: 12), Expanded( child: Text( text, @@ -371,6 +582,7 @@ class _LoginFormState extends State { color: Colors.white70, fontSize: 14, height: 1.5, + fontWeight: FontWeight.w400, ), ), ),