simulacion de estados y flujo de notificacion, modificacion de estilos en todas las vistas
This commit is contained in:
@@ -48,7 +48,6 @@ class _EditProfileScreenState extends ConsumerState<EditProfileScreen> {
|
||||
_prefilled = true;
|
||||
}
|
||||
|
||||
// Normaliza un teléfono almacenado (con o sin lada/guiones) al formato 000-000-0000
|
||||
String _formatPhoneInitial(String? raw) {
|
||||
if (raw == null || raw.isEmpty) return '';
|
||||
final digits = raw.replaceAll(RegExp(r'\D'), '');
|
||||
@@ -132,7 +131,6 @@ class _EditProfileScreenState extends ConsumerState<EditProfileScreen> {
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.background,
|
||||
appBar: AppBar(title: const Text('Editar perfil')),
|
||||
body: userAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(
|
||||
@@ -154,114 +152,225 @@ class _EditProfileScreenState extends ConsumerState<EditProfileScreen> {
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const AppSectionTitle(title: 'Datos personales'),
|
||||
AppCard(
|
||||
child: Column(
|
||||
children: [
|
||||
AppFormField(
|
||||
label: 'Nombre',
|
||||
controller: _nameCtrl,
|
||||
validator: (v) => (v == null || v.trim().isEmpty)
|
||||
? 'Ingresa tu nombre'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppFormField(
|
||||
label: 'Correo electrónico',
|
||||
controller: _emailCtrl,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) {
|
||||
if (v == null || v.trim().isEmpty) return null;
|
||||
if (!v.contains('@')) return 'Correo inválido';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_PhoneField(controller: _phoneCtrl),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const AppSectionTitle(title: 'Cambiar contraseña'),
|
||||
AppCard(
|
||||
child: Column(
|
||||
children: [
|
||||
AppFormField(
|
||||
label: 'Contraseña actual',
|
||||
controller: _currentPasswordCtrl,
|
||||
obscureText: true,
|
||||
validator: (v) {
|
||||
if (!_wantsPasswordChange) return null;
|
||||
if (v == null || v.length < 6) {
|
||||
return 'Mínimo 6 caracteres';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppFormField(
|
||||
label: 'Nueva contraseña',
|
||||
controller: _newPasswordCtrl,
|
||||
obscureText: true,
|
||||
validator: (v) {
|
||||
if (!_wantsPasswordChange) return null;
|
||||
if (v == null || v.length < 6) {
|
||||
return 'Mínimo 6 caracteres';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppFormField(
|
||||
label: 'Confirmar nueva contraseña',
|
||||
controller: _confirmPasswordCtrl,
|
||||
obscureText: true,
|
||||
validator: (v) {
|
||||
if (!_wantsPasswordChange) return null;
|
||||
if (v == null || v.isEmpty) {
|
||||
return 'Confirma la contraseña';
|
||||
}
|
||||
if (v != _newPasswordCtrl.text) return 'No coincide';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Déjalo en blanco si no deseas cambiarla.',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: _buildPageHeader(context)),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
const AppSectionTitle(title: 'Datos personales'),
|
||||
AppFormCard(
|
||||
icon: Icons.person_outline,
|
||||
title: 'Información personal',
|
||||
child: Column(
|
||||
children: [
|
||||
AppFormField(
|
||||
label: 'Nombre',
|
||||
controller: _nameCtrl,
|
||||
validator: (v) =>
|
||||
(v == null || v.trim().isEmpty)
|
||||
? 'Ingresa tu nombre'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppFormField(
|
||||
label: 'Correo electrónico',
|
||||
controller: _emailCtrl,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) {
|
||||
if (v == null || v.trim().isEmpty) return null;
|
||||
if (!v.contains('@')) return 'Correo inválido';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_PhoneField(controller: _phoneCtrl),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
const AppSectionTitle(title: 'Cambiar contraseña'),
|
||||
AppFormCard(
|
||||
icon: Icons.lock_outline,
|
||||
title: 'Seguridad',
|
||||
child: Column(
|
||||
children: [
|
||||
AppFormField(
|
||||
label: 'Contraseña actual',
|
||||
controller: _currentPasswordCtrl,
|
||||
obscureText: true,
|
||||
validator: (v) {
|
||||
if (!_wantsPasswordChange) return null;
|
||||
if (v == null || v.length < 6) {
|
||||
return 'Mínimo 6 caracteres';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppFormField(
|
||||
label: 'Nueva contraseña',
|
||||
controller: _newPasswordCtrl,
|
||||
obscureText: true,
|
||||
validator: (v) {
|
||||
if (!_wantsPasswordChange) return null;
|
||||
if (v == null || v.length < 6) {
|
||||
return 'Mínimo 6 caracteres';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppFormField(
|
||||
label: 'Confirmar nueva contraseña',
|
||||
controller: _confirmPasswordCtrl,
|
||||
obscureText: true,
|
||||
validator: (v) {
|
||||
if (!_wantsPasswordChange) return null;
|
||||
if (v == null || v.isEmpty) {
|
||||
return 'Confirma la contraseña';
|
||||
}
|
||||
if (v != _newPasswordCtrl.text) {
|
||||
return 'No coincide';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Déjalo en blanco si no deseas cambiarla.',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
]),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _saving ? null : _save,
|
||||
child: _saving
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Guardar cambios'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: _buildSaveButton(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageHeader(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
20,
|
||||
MediaQuery.of(context).padding.top + 12,
|
||||
20,
|
||||
24,
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xFF4A0E26), Color(0xFF9B1B4A)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(28),
|
||||
bottomRight: Radius.circular(28),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Editar perfil',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'Actualiza tu información personal',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _saving ? null : _save,
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: _saving
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.save_outlined, color: Colors.white, size: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSaveButton() {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 12, 24, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _saving ? null : _save,
|
||||
child: _saving
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text('Guardar cambios'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -369,7 +478,7 @@ class _PhoneField extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return null; // opcional
|
||||
if (v == null || v.isEmpty) return null;
|
||||
final digits = v.replaceAll('-', '');
|
||||
if (digits.length != 10) {
|
||||
return 'Ingresa exactamente 10 dígitos';
|
||||
|
||||
Reference in New Issue
Block a user