From c91d1f298eb9ec66ea4ada2ec627db7eb3d4282e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20M=C3=A9ndez?= Date: Sat, 23 May 2026 03:41:03 -0600 Subject: [PATCH 1/3] fix: actualizar la forma de aplicar opacidad en colores de tema y componentes de la interfaz, y se arreglaron errores de interfaz --- lib/core/theme/app_theme.dart | 6 +- .../screens/home_screen_placeholder.dart | 10 +- .../presentation/screens/login_screen.dart | 314 +++++++++--------- .../presentation/screens/register_screen.dart | 2 +- pubspec.lock | 102 ++++-- 5 files changed, 243 insertions(+), 191 deletions(-) diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 23110a2..79d6cda 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -55,7 +55,7 @@ abstract final class AppTheme { borderRadius: BorderRadius.circular(12), ), elevation: 3, - shadowColor: leafGreen.withOpacity(0.4), + shadowColor: leafGreen.withValues(alpha: 0.4), textStyle: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, @@ -89,12 +89,12 @@ abstract final class AppTheme { borderSide: const BorderSide(color: errorRed, width: 2), ), labelStyle: const TextStyle(color: midGray), - hintStyle: TextStyle(color: midGray.withOpacity(0.7)), + hintStyle: TextStyle(color: midGray.withValues(alpha: 0.7)), ), cardTheme: CardThemeData( color: Colors.white, elevation: 2, - shadowColor: Colors.black.withOpacity(0.08), + shadowColor: Colors.black.withValues(alpha: 0.08), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), diff --git a/lib/features/auth/presentation/screens/home_screen_placeholder.dart b/lib/features/auth/presentation/screens/home_screen_placeholder.dart index d3e844e..5ee44a3 100644 --- a/lib/features/auth/presentation/screens/home_screen_placeholder.dart +++ b/lib/features/auth/presentation/screens/home_screen_placeholder.dart @@ -122,7 +122,7 @@ class _WelcomeCard extends StatelessWidget { borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: AppTheme.leafGreen.withOpacity(0.3), + color: AppTheme.leafGreen.withValues(alpha: 0.3), blurRadius: 16, offset: const Offset(0, 6), ), @@ -134,7 +134,7 @@ class _WelcomeCard extends StatelessWidget { width: 52, height: 52, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(13), ), child: const Icon( @@ -169,7 +169,7 @@ class _WelcomeCard extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), ), child: Text( @@ -307,10 +307,10 @@ class _PreventiveMessageCard extends StatelessWidget { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppTheme.alertAmber.withOpacity(0.08), + color: AppTheme.alertAmber.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(14), border: Border.all( - color: AppTheme.alertAmber.withOpacity(0.3), + color: AppTheme.alertAmber.withValues(alpha: 0.3), ), ), child: Column( diff --git a/lib/features/auth/presentation/screens/login_screen.dart b/lib/features/auth/presentation/screens/login_screen.dart index 384b7d4..7cbf586 100644 --- a/lib/features/auth/presentation/screens/login_screen.dart +++ b/lib/features/auth/presentation/screens/login_screen.dart @@ -8,15 +8,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}); @@ -31,6 +22,7 @@ class _LoginScreenState extends State final _passwordController = TextEditingController(); bool _obscurePassword = true; + late final AnimationController _animController; late final Animation _fadeAnim; late final Animation _slideAnim; @@ -42,17 +34,9 @@ class _LoginScreenState extends State vsync: this, duration: const Duration(milliseconds: 700), ); - _fadeAnim = CurvedAnimation( - parent: _animController, - curve: Curves.easeOut, - ); - _slideAnim = Tween( - begin: const Offset(0, 0.06), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _animController, - curve: Curves.easeOutCubic, - )); + _fadeAnim = CurvedAnimation(parent: _animController, curve: Curves.easeOut); + _slideAnim = Tween(begin: const Offset(0, 0.06), end: Offset.zero) + .animate(CurvedAnimation(parent: _animController, curve: Curves.easeOutCubic)); _animController.forward(); } @@ -68,7 +52,7 @@ class _LoginScreenState extends State if (_formKey.currentState?.validate() ?? false) { context.read().add( AuthLoginRequested( - identifier: _identifierController.text, + identifier: _identifierController.text.trim(), password: _passwordController.text, ), ); @@ -113,151 +97,179 @@ class _LoginScreenState extends State 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 Spacer(), + 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), ], ), ), - ), 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), + Widget _buildHeader() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'Bienvenido a WasteNotify', + style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + 'Inicia sesión para continuar', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ); + } - // --- 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), + Widget _buildForm(BuildContext context) { + return Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 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))), + ), + validator: (value) => (value == null || value.trim().isEmpty) + ? 'Por favor, ingresa tus datos de acceso' + : null, + ), + const SizedBox(height: 20), - // --- CONTENEDOR REACTIVO DE BOTONES (LOGIN & REGISTER) --- - // --- CONTENEDOR REACTIVO DE BOTONES (LOGIN & REGISTER) --- - BlocBuilder( - builder: (context, state) { - final isLoading = state is AuthLoading; + TextFormField( + controller: _passwordController, + obscureText: _obscurePassword, + decoration: InputDecoration( + labelText: 'Contraseña de Acceso', + prefixIcon: const Icon(Icons.lock_outline_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword ? Icons.visibility : Icons.visibility_off), + onPressed: () => setState(() => _obscurePassword = !_obscurePassword), + ), + ), + validator: (value) => (value == null || value.isEmpty) + ? 'Por favor, introduce tu contraseña' + : null, + ), + const SizedBox(height: 32), - return Column( - mainAxisSize: MainAxisSize.min, - 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), - ), - ], + BlocBuilder( + builder: (context, state) { + final isLoading = state is AuthLoading; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: isLoading + ? [] + : [ + BoxShadow( + color: AppTheme.leafGreen.withValues(alpha: 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), - ), - ], - ), - ), - ), - - // 📍 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, - ), + ], + ), + child: ElevatedButton( + onPressed: isLoading ? null : () => _submit(context), + style: ElevatedButton.styleFrom( + backgroundColor: isLoading + ? AppTheme.mintGreen.withValues(alpha: 0.7) + : AppTheme.leafGreen, + disabledBackgroundColor: + AppTheme.mintGreen.withValues(alpha: 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), + ), + ], + ), + ), ), - ], // 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í) + const SizedBox(height: 20), + TextButton( + onPressed: () => context.push('/register'), + child: const Text( + '¿No tienes una cuenta ciudadana? Regístrate aquí', + style: TextStyle( + color: AppTheme.leafGreen, + fontWeight: FontWeight.w700, + fontSize: 13, + ), + ), + ) + ], + ); + }, + ), + ], + ), + ); + } + + Widget _buildDemoHint() { + return const Text( + 'Credenciales de demo: ciudadano@ejemplo.com / password123', + style: TextStyle(fontSize: 12, color: Colors.black54), + ); + } +} diff --git a/lib/features/auth/presentation/screens/register_screen.dart b/lib/features/auth/presentation/screens/register_screen.dart index a49cff7..6b49405 100644 --- a/lib/features/auth/presentation/screens/register_screen.dart +++ b/lib/features/auth/presentation/screens/register_screen.dart @@ -57,7 +57,7 @@ class _RegisterScreenState extends State { // Dropdown para seleccionar la Colonia (Requisito MVP) DropdownButtonFormField( - value: _selectedColonia, + initialValue: _selectedColonia, decoration: const InputDecoration(labelText: 'Selecciona tu Colonia / Domicilio', border: OutlineInputBorder()), items: MockWasteData.schedules.map((zone) { return DropdownMenuItem(value: zone.colonia, child: Text(zone.colonia)); diff --git a/pubspec.lock b/pubspec.lock index c255b1f..dbb28a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,26 +37,26 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -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: @@ -77,10 +85,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: @@ -176,30 +184,62 @@ 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: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -236,26 +276,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.18.0" mgrs_dart: dependency: transitive description: @@ -276,10 +316,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider_linux: dependency: transitive description: @@ -425,18 +465,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -457,10 +497,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.11" timezone: dependency: transitive description: @@ -489,10 +529,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -534,5 +574,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.10.0-0 <4.0.0" flutter: ">=3.27.0" -- 2.49.1 From 6ae45fd371c7c6d526e9b7c4204891111ead6d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20M=C3=A9ndez?= Date: Sat, 23 May 2026 03:55:45 -0600 Subject: [PATCH 2/3] fix: actualizar la forma de aplicar opacidad en colores de tema en PrivacyNoticeCard y ScheduleWarningBanner --- .../auth/presentation/widgets/privacy_notice_card.dart | 8 ++++---- test/widget_test.dart | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/features/auth/presentation/widgets/privacy_notice_card.dart b/lib/features/auth/presentation/widgets/privacy_notice_card.dart index 04adb88..8e52e19 100644 --- a/lib/features/auth/presentation/widgets/privacy_notice_card.dart +++ b/lib/features/auth/presentation/widgets/privacy_notice_card.dart @@ -17,7 +17,7 @@ class PrivacyNoticeCard extends StatelessWidget { color: AppTheme.lightMint, borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppTheme.mintGreen.withOpacity(0.3), + color: AppTheme.mintGreen.withValues(alpha: 0.3), width: 1, ), ), @@ -85,7 +85,7 @@ class _PrivacyPoint extends StatelessWidget { text, style: TextStyle( fontSize: 12, - color: AppTheme.forestGreen.withOpacity(0.85), + color: AppTheme.forestGreen.withValues(alpha: 0.85), height: 1.4, ), ), @@ -105,10 +105,10 @@ class ScheduleWarningBanner extends StatelessWidget { return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( - color: AppTheme.alertAmber.withOpacity(0.10), + color: AppTheme.alertAmber.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(10), border: Border.all( - color: AppTheme.alertAmber.withOpacity(0.35), + color: AppTheme.alertAmber.withValues(alpha: 0.35), width: 1, ), ), diff --git a/test/widget_test.dart b/test/widget_test.dart index e69304c..b203823 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; // Use a local test app to avoid depending on external package imports // which may not exist in the test environment. class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); @override State createState() => _MyAppState(); -- 2.49.1 From c560fb09fb28a59557e3c4d4383f28f2300dedb1 Mon Sep 17 00:00:00 2001 From: 25030248hasel Date: Sat, 23 May 2026 04:17:45 -0600 Subject: [PATCH 3/3] FIX: VIEW REGISTER --- lib/core/router/app_router.dart | 43 ++++------ .../presentation/screens/login_screen.dart | 84 ++++++++----------- .../presentation/screens/register_screen.dart | 33 ++++++-- 3 files changed, 74 insertions(+), 86 deletions(-) diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 4c5b78f..eec569e 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -1,25 +1,22 @@ import 'dart:async'; -import 'package:flutter_application_1/features/auth/presentation/screens/register_screen.dart'; - import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../features/auth/presentation/bloc/auth_bloc.dart'; import '../../features/auth/presentation/bloc/auth_state.dart'; import '../../features/auth/presentation/screens/login_screen.dart'; +import '../../features/auth/presentation/screens/register_screen.dart'; import '../../features/auth/presentation/screens/home_screen_placeholder.dart'; /// Rutas nombradas de la aplicación. abstract final class AppRoutes { static const String splash = '/'; static const String login = '/login'; + static const String register = '/register'; // 📍 Agregada constante oficial static const String home = '/home'; } /// Configuración central de navegación con go_router. -/// -/// La redirección basada en estado de autenticación garantiza que -/// rutas protegidas sean inaccesibles sin sesión válida. GoRouter createRouter(AuthBloc authBloc) { return GoRouter( initialLocation: AppRoutes.splash, @@ -27,8 +24,7 @@ GoRouter createRouter(AuthBloc authBloc) { redirect: (BuildContext context, GoRouterState state) { final authState = authBloc.state; final isAuthenticated = authState is AuthAuthenticated; - final isCheckingSession = - authState is AuthCheckingSession || authState is AuthInitial; + final isCheckingSession = authState is AuthCheckingSession || authState is AuthInitial; final currentLocation = state.uri.path; // Mientras se verifica la sesión, mostrar splash. @@ -36,15 +32,17 @@ GoRouter createRouter(AuthBloc authBloc) { return currentLocation == AppRoutes.splash ? null : AppRoutes.splash; } - // Si no está autenticado, ir a login. + // 📍 SOLUCCIÓN: Permitir acceso a rutas públicas sin estar autenticado + final publicRoutes = [AppRoutes.login, AppRoutes.register]; + final isPublicRoute = publicRoutes.contains(currentLocation); + if (!isAuthenticated) { - return currentLocation == AppRoutes.login ? null : AppRoutes.login; + // Si no está autenticado y no está en una ruta pública, mandarlo a login + return isPublicRoute ? null : AppRoutes.login; } - // Si está autenticado y en splash o login, ir a home. - if (isAuthenticated && - (currentLocation == AppRoutes.login || - currentLocation == AppRoutes.splash)) { + // Si está autenticado e intenta ir a login o registro, mandarlo a home + if (isAuthenticated && isPublicRoute) { return AppRoutes.home; } @@ -60,21 +58,13 @@ GoRouter createRouter(AuthBloc authBloc) { builder: (context, state) => const LoginScreen(), ), GoRoute( - path: AppRoutes.home, - builder: (context, state) => const HomeScreenPlaceholder(), + path: AppRoutes.register, // 📍 Usando la constante limpia + builder: (context, state) => const RegisterScreen(), ), - // 📍 Agrega el import arriba si te lo pide: -// import 'package:flutter_application_1/features/auth/presentation/screens/register_screen.dart'; - GoRoute( path: AppRoutes.home, builder: (context, state) => const HomeScreenPlaceholder(), ), - // 📍 CORRECCIÓN: Usamos la constante oficial del proyecto en lugar del texto a mano - GoRoute( - path: '/register', // 📍 CORRECCIÓN: Texto plano limpio entre comillas - builder: (context, state) => const RegisterScreen(), - ), // GoRoute ], ); } @@ -91,11 +81,7 @@ class _SplashScreen extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.recycling_rounded, - size: 72, - color: Colors.white, - ), + const Icon(Icons.recycling_rounded, size: 72, color: Colors.white), const SizedBox(height: 24), Text( 'WasteNotify', @@ -117,7 +103,6 @@ class _SplashScreen extends StatelessWidget { } /// Notificador que conecta el estado del BLoC con go_router. -/// Permite que el router reaccione automáticamente a cambios de sesión. class GoRouterAuthNotifier extends ChangeNotifier { final AuthBloc _authBloc; late final StreamSubscription _subscription; diff --git a/lib/features/auth/presentation/screens/login_screen.dart b/lib/features/auth/presentation/screens/login_screen.dart index 7cbf586..4c5695e 100644 --- a/lib/features/auth/presentation/screens/login_screen.dart +++ b/lib/features/auth/presentation/screens/login_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import '../../../../core/router/app_router.dart'; // 📍 Importación de rutas verificada import '../../../../core/theme/app_theme.dart'; import '../bloc/auth_bloc.dart'; @@ -36,7 +37,8 @@ class _LoginScreenState extends State ); _fadeAnim = CurvedAnimation(parent: _animController, curve: Curves.easeOut); _slideAnim = Tween(begin: const Offset(0, 0.06), end: Offset.zero) - .animate(CurvedAnimation(parent: _animController, curve: Curves.easeOutCubic)); + .animate(CurvedAnimation( + parent: _animController, curve: Curves.easeOutCubic)); _animController.forward(); } @@ -68,7 +70,8 @@ class _LoginScreenState extends State SnackBar( content: Row( children: [ - const Icon(Icons.error_outline, color: Colors.white, size: 18), + const Icon(Icons.error_outline, + color: Colors.white, size: 18), const SizedBox(width: 10), Expanded(child: Text(state.message)), ], @@ -104,13 +107,15 @@ class _LoginScreenState extends State _buildDemoHint(), const Spacer(), 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), + Center( + child: 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), ], @@ -163,7 +168,6 @@ class _LoginScreenState extends State : null, ), const SizedBox(height: 20), - TextFormField( controller: _passwordController, obscureText: _obscurePassword, @@ -175,7 +179,8 @@ class _LoginScreenState extends State suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off), - onPressed: () => setState(() => _obscurePassword = !_obscurePassword), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), ), ), validator: (value) => (value == null || value.isEmpty) @@ -183,7 +188,6 @@ class _LoginScreenState extends State : null, ), const SizedBox(height: 32), - BlocBuilder( builder: (context, state) { final isLoading = state is AuthLoading; @@ -200,7 +204,8 @@ class _LoginScreenState extends State ? [] : [ BoxShadow( - color: AppTheme.leafGreen.withValues(alpha: 0.3), + color: + AppTheme.leafGreen.withValues(alpha: 0.3), blurRadius: 16, offset: const Offset(0, 6), ), @@ -220,43 +225,26 @@ class _LoginScreenState extends State ), child: isLoading ? const SizedBox( - height: 22, - width: 22, + height: 20, + width: 20, child: CircularProgressIndicator( - strokeWidth: 2.5, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), + color: Colors.white, strokeWidth: 2), ) - : 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 Text('Iniciar Sesión', + style: TextStyle(color: Colors.white)), ), ), - - const SizedBox(height: 20), + const SizedBox(height: 16), + // 📍 BOTÓN DE REGISTRO CORREGIDO (child al final) TextButton( - onPressed: () => context.push('/register'), - child: const Text( - '¿No tienes una cuenta ciudadana? Regístrate aquí', - style: TextStyle( - color: AppTheme.leafGreen, - fontWeight: FontWeight.w700, - fontSize: 13, - ), + onPressed: () => context.push(AppRoutes.register), + style: TextButton.styleFrom( + foregroundColor: AppTheme.leafGreen, + textStyle: const TextStyle( + fontSize: 14, fontWeight: FontWeight.w500), ), - ) + child: const Text('¿No tienes cuenta? Regístrate aquí'), + ), ], ); }, @@ -266,10 +254,6 @@ class _LoginScreenState extends State ); } - Widget _buildDemoHint() { - return const Text( - 'Credenciales de demo: ciudadano@ejemplo.com / password123', - style: TextStyle(fontSize: 12, color: Colors.black54), - ); - } + // Placeholders para evitar errores si no están definidos + Widget _buildDemoHint() => const SizedBox.shrink(); } diff --git a/lib/features/auth/presentation/screens/register_screen.dart b/lib/features/auth/presentation/screens/register_screen.dart index 6b49405..8b01016 100644 --- a/lib/features/auth/presentation/screens/register_screen.dart +++ b/lib/features/auth/presentation/screens/register_screen.dart @@ -11,11 +11,31 @@ class RegisterScreen extends StatefulWidget { class _RegisterScreenState extends State { final _formKey = GlobalKey(); - String _selectedColonia = 'Centro'; + late String _selectedColonia; // 📍 Se mantiene late pero se inicializa abajo final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); + // 📍 CORRECCIÓN CRÍTICA: Asignar el valor inicial antes del método build + @override + void initState() { + super.initState(); + if (MockWasteData.schedules.isNotEmpty) { + // Evita problemas de aserción tomando el primer valor real de tus mocks + _selectedColonia = MockWasteData.schedules.first.colonia; + } else { + _selectedColonia = ''; // Respaldo por si la lista estuviera vacía + } + } + + @override + void dispose() { + _nameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -38,26 +58,26 @@ class _RegisterScreenState extends State { TextFormField( controller: _nameController, decoration: const InputDecoration(labelText: 'Nombre Completo', border: OutlineInputBorder()), - validator: (v) => v!.isEmpty ? 'Ingresa tu nombre' : null, + validator: (v) => v == null || v.isEmpty ? 'Ingresa tu nombre' : null, ), const SizedBox(height: 16), TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Correo o Teléfono', border: OutlineInputBorder()), - validator: (v) => v!.isEmpty ? 'Ingresa tus datos' : null, + validator: (v) => v == null || v.isEmpty ? 'Ingresa tus datos' : null, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, obscureText: true, decoration: const InputDecoration(labelText: 'Contraseña', border: OutlineInputBorder()), - validator: (v) => v!.length < 6 ? 'Mínimo 6 caracteres' : null, + validator: (v) => v == null || v.length < 6 ? 'Mínimo 6 caracteres' : null, ), const SizedBox(height: 16), - // Dropdown para seleccionar la Colonia (Requisito MVP) + // Dropdown para seleccionar la Colonia DropdownButtonFormField( - initialValue: _selectedColonia, + value: _selectedColonia, decoration: const InputDecoration(labelText: 'Selecciona tu Colonia / Domicilio', border: OutlineInputBorder()), items: MockWasteData.schedules.map((zone) { return DropdownMenuItem(value: zone.colonia, child: Text(zone.colonia)); @@ -82,7 +102,6 @@ class _RegisterScreenState extends State { style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.green), onPressed: () { if (_formKey.currentState!.validate()) { - // Simulación de Registro Exitoso: Navega al Home enviando la colonia elegida ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Cuenta creada con éxito (Modo Simulación)')), ); -- 2.49.1