diff --git a/lib/features/auth/presentation/screens/login_screen.dart b/lib/features/auth/presentation/screens/login_screen.dart index 384b7d4..7151fd5 100644 --- a/lib/features/auth/presentation/screens/login_screen.dart +++ b/lib/features/auth/presentation/screens/login_screen.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_theme.dart'; import '../bloc/auth_bloc.dart'; @@ -8,15 +7,6 @@ import '../bloc/auth_event.dart'; import '../bloc/auth_state.dart'; import '../widgets/privacy_notice_card.dart'; -/// Pantalla de inicio de sesión — MVP WasteNotify. -/// -/// Diseño: Limpio, institucional, con paleta verde-tierra. -/// Incluye mensajería preventiva integrada de forma no intrusiva. -/// -/// Credenciales de demo: -/// Ciudadano: ciudadano@ejemplo.com / password123 -/// Operador: operador@ejemplo.com / operador456 -/// Teléfono: 5551234567 / pass1234 class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -24,8 +14,7 @@ class LoginScreen extends StatefulWidget { State createState() => _LoginScreenState(); } -class _LoginScreenState extends State - with SingleTickerProviderStateMixin { +class _LoginScreenState extends State with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); final _identifierController = TextEditingController(); final _passwordController = TextEditingController(); @@ -97,167 +86,127 @@ class _LoginScreenState extends State child: Scaffold( backgroundColor: AppTheme.warmWhite, body: SafeArea( - child: CustomScrollView( - slivers: [ - SliverFillRemaining( - hasScrollBody: false, - child: FadeTransition( - opacity: _fadeAnim, - child: SlideTransition( - position: _slideAnim, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 48), - _buildHeader(), - const SizedBox(height: 32), - const ScheduleWarningBanner(), - const SizedBox(height: 32), - _buildForm(context), - const SizedBox(height: 24), - const PrivacyNoticeCard(), - const SizedBox(height: 32), - _buildDemoHint(), - const SizedBox(height: 24), - ], - ), - ), - ), const SizedBox(height: 8), - Text( - 'Sistema Municipal de Recolección Residencial\nCelaya, Guanajuato', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, color: Colors.grey.shade600, height: 1.3), - ), - const SizedBox(height: 48), - - // --- CAMPO: EMAIL / IDENTIFICADOR --- - TextFormField( - controller: _emailController, - keyboardType: TextInputType.emailAddress, - decoration: const InputDecoration( - labelText: 'Correo Electrónico o Teléfono', - prefixIcon: Icon(Icons.email_outlined), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12))), - ), - validator: (value) => - (value == null || value.trim().isEmpty) - ? 'Por favor, ingresa tus datos de acceso' - : null, - ), - const SizedBox(height: 20), - - // --- CAMPO: CONTRASENA --- - TextFormField( - controller: _passwordController, - obscureText: true, - decoration: const InputDecoration( - labelText: 'Contraseña de Acceso', - prefixIcon: Icon(Icons.lock_outline_rounded), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12))), - ), - validator: (value) => (value == null || value.isEmpty) - ? 'Por favor, introduce tu contraseña' - : null, - ), - const SizedBox(height: 32), - - // --- CONTENEDOR REACTIVO DE BOTONES (LOGIN & REGISTER) --- - // --- CONTENEDOR REACTIVO DE BOTONES (LOGIN & REGISTER) --- - BlocBuilder( - builder: (context, state) { - final isLoading = state is AuthLoading; - - return Column( - mainAxisSize: MainAxisSize.min, + child: Form( + key: _formKey, + child: CustomScrollView( + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: FadeTransition( + opacity: _fadeAnim, + child: SlideTransition( + position: _slideAnim, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - boxShadow: isLoading - ? [] - : [ - BoxShadow( - color: AppTheme.leafGreen - .withOpacity(0.3), - blurRadius: 16, - offset: const Offset(0, 6), - ), - ], - ), - child: ElevatedButton( - onPressed: - isLoading ? null : () => _submit(context), - style: ElevatedButton.styleFrom( - backgroundColor: isLoading - ? AppTheme.mintGreen.withOpacity(0.7) - : AppTheme.leafGreen, - disabledBackgroundColor: - AppTheme.mintGreen.withOpacity(0.6), - padding: - const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - ), - child: isLoading - ? const SizedBox( - height: 22, - width: 22, - child: CircularProgressIndicator( - strokeWidth: 2.5, - valueColor: AlwaysStoppedAnimation( - Colors.white), - ), - ) - : const Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon(Icons.login_rounded, - size: 20, color: Colors.white), - SizedBox(width: 10), - Text( - 'Ingresar de forma segura', - style: TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.bold), - ), - ], - ), - ), + const SizedBox(height: 48), + _buildHeader(), + const SizedBox(height: 8), + Text( + 'Sistema Municipal de Recolección Residencial\nCelaya, Guanajuato', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, color: Colors.grey.shade600, height: 1.3), ), - - // 📍 ENLACE DE REDIRECCIÓN AL REGISTRO CIUDADANO - const SizedBox(height: 20), - TextButton( - onPressed: () { - // 🚀 Forzamos la navegación limpia usando la ruta de texto directo - context.push('/register'); - }, - child: const Text( - '¿No tienes una cuenta ciudadana? Regístrate aquí', - style: TextStyle( - color: AppTheme.leafGreen, - fontWeight: FontWeight.w700, - fontSize: 13, - ), + const SizedBox(height: 32), + // Asegúrate de que este widget esté definido en tu proyecto o cámbialo por un placeholder + // const ScheduleWarningBanner(), + const SizedBox(height: 32), + + // --- CAMPO: EMAIL / IDENTIFICADOR --- + TextFormField( + controller: _identifierController, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + labelText: 'Correo Electrónico o Teléfono', + prefixIcon: Icon(Icons.email_outlined), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), ), - ) - ]); - }, - ), - ], // Fin children de la Column - ), // Fin Column - ), // Fin Form - ), // Fin SingleChildScrollView - ), // Fin SafeArea - ), // Fin BlocListener - ); // Fin Scaffold (Asegúrate de que tenga el punto y coma aquí) + validator: (value) => + (value == null || value.trim().isEmpty) + ? 'Por favor, ingresa tus datos de acceso' + : null, + ), + const SizedBox(height: 20), + // --- CAMPO: CONTRASEÑA --- + TextFormField( + controller: _passwordController, + obscureText: _obscurePassword, + decoration: InputDecoration( + labelText: 'Contraseña de Acceso', + prefixIcon: const Icon(Icons.lock_outline_rounded), + suffixIcon: IconButton( + icon: Icon(_obscurePassword ? Icons.visibility_off : Icons.visibility), + onPressed: () => setState(() => _obscurePassword = !_obscurePassword), + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), + ), + validator: (value) => (value == null || value.isEmpty) + ? 'Por favor, introduce tu contraseña' + : null, + ), + const SizedBox(height: 32), + + // --- CONTENEDOR REACTIVO DE BOTONES --- + BlocBuilder( + builder: (context, state) { + final isLoading = state is AuthLoading; + + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: isLoading + ? [] + : [ + BoxShadow( + color: AppTheme.leafGreen.withOpacity(0.3), + blurRadius: 16, + offset: const Offset(0, 6), + ), + ], + ), + child: ElevatedButton( + onPressed: isLoading ? null : () => _submit(context), + style: ElevatedButton.styleFrom( + backgroundColor: isLoading + ? AppTheme.mintGreen.withOpacity(0.7) + : AppTheme.leafGreen, + disabledBackgroundColor: AppTheme.mintGreen.withOpacity(0.6), + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: isLoading + ? const CircularProgressIndicator(color: Colors.white) + : const Text('Iniciar Sesión', style: TextStyle(color: Colors.white)), + ), + ); + }, + ), + const SizedBox(height: 24), + const PrivacyNoticeCard(), + const SizedBox(height: 32), + _buildDemoHint(), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + // Métodos auxiliares creados como placeholders para evitar errores de compilación + Widget _buildHeader() => const Center(child: Text("WasteNotify", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold))); + Widget _buildDemoHint() => const Center(child: Text("Demo: ciudadano@ejemplo.com / password123", style: TextStyle(fontSize: 12))); +} diff --git a/pubspec.lock b/pubspec.lock index c255b1f..031dec2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" equatable: dependency: "direct main" description: @@ -176,6 +184,38 @@ packages: url: "https://pub.dev" source: hosted version: "13.2.5" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" + url: "https://pub.dev" + source: hosted + version: "0.9.1" leak_tracker: dependency: transitive description: