feat: migrate prosapp_web_app from Firebase to NestJS API
- Replace Firebase Auth with JWT stored in SharedPreferences - Replace Firestore with REST API calls via new ApiService - Replace Firebase Storage with POST /storage/upload - Remove firebase_auth, firebase_core, cloud_firestore, firebase_storage, firebase_messaging, google_sign_in deps - Add api_service.dart as central HTTP client Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1c20d0c0cd
commit
b95e0630de
@@ -1,14 +1,13 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_storage/firebase_storage.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||
import 'package:prosappco/src/components/photo_view.dart';
|
||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||
import 'package:prosappco/src/controllers/add_name_email_city.dart';
|
||||
import 'package:prosappco/src/presentation/widgets/profile/birth_date_picker.dart';
|
||||
@@ -18,11 +17,11 @@ import 'package:prosappco/src/presentation/screens/new_password.dart';
|
||||
import 'package:prosappco/src/presentation/widgets/shared/primary_checkbox.dart';
|
||||
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
||||
import 'package:prosappco/src/providers/user_provider.dart';
|
||||
import 'package:prosappco/src/services/api_service.dart';
|
||||
import 'package:prosappco/src/services/select_image_profile.dart';
|
||||
import 'package:prosappco/src/presentation/widgets/profile/gender_dropdown.dart';
|
||||
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../components/photo_view.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
@@ -34,6 +33,7 @@ class ProfileScreen extends StatefulWidget {
|
||||
|
||||
class _ProfileScreenState extends State<ProfileScreen> {
|
||||
File? imagen_to_upload;
|
||||
Uint8List? webImageBytes;
|
||||
final DateFormat formatter = DateFormat('dd/MM/yyyy');
|
||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||
bool _obscureText = true;
|
||||
@@ -43,522 +43,108 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
final _nameController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
late final FirebaseAuth _auth;
|
||||
final FirebaseStorage storage = FirebaseStorage.instance;
|
||||
var photoTemp = '';
|
||||
|
||||
var _ciudad = '...';
|
||||
var _photo = '..../images/perfil-2.png';
|
||||
String? _email = '';
|
||||
String? _photoUrl;
|
||||
String? _email;
|
||||
String gender = '';
|
||||
String genderDb = '';
|
||||
DateTime? birthDate;
|
||||
String birthDateDb = '';
|
||||
bool enableLoginWithEmail = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_auth = FirebaseAuth.instance;
|
||||
|
||||
final currentUser = _auth.currentUser;
|
||||
|
||||
if (currentUser != null && currentUser.phoneNumber != null) {
|
||||
_phoneNumberController.text = currentUser.phoneNumber!;
|
||||
}
|
||||
|
||||
if (currentUser != null && currentUser.displayName != null) {
|
||||
_nameController.text = currentUser.displayName!;
|
||||
}
|
||||
if (currentUser != null && currentUser.email != null) {
|
||||
_emailController.text = currentUser.email!;
|
||||
}
|
||||
|
||||
if (gender.isEmpty) {
|
||||
AuthenticationRepository.instance.getGender(uid.toString()).then(
|
||||
(String s) => setState(() {
|
||||
genderDb = s;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (birthDate == null) {
|
||||
AuthenticationRepository.instance.getBirthday(uid.toString()).then(
|
||||
(String s) => setState(() {
|
||||
if (s.isNotEmpty) {
|
||||
birthDateDb = s;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
_email = currentUser?.email;
|
||||
|
||||
if (_ciudad == '...') {
|
||||
AuthenticationRepository.instance.getCity(uid.toString()).then(
|
||||
(String s) => setState(() {
|
||||
_ciudad = s;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (_photo == '...') {
|
||||
AuthenticationRepository.instance.getPhoto(uid.toString()).then(
|
||||
(String s) => setState(() {
|
||||
_photo = s;
|
||||
}),
|
||||
);
|
||||
}
|
||||
_loadCurrentUser();
|
||||
}
|
||||
|
||||
Future<void> updateImage(image) async {
|
||||
try {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.update({'photo': image});
|
||||
} catch (e) {
|
||||
try {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.set({'photo': image});
|
||||
} catch (e) {
|
||||
print('Error al agregar la imagen de perfil: $e');
|
||||
}
|
||||
|
||||
print('Error al actualizar la imagen de perfil: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> uploadImage(File image) async {
|
||||
final now = DateTime.now();
|
||||
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
||||
final milliseconds = (now.microsecondsSinceEpoch / 1000).round();
|
||||
final random = '$formattedDate$milliseconds';
|
||||
|
||||
Reference ref =
|
||||
storage.ref().child('users').child(uid!).child('profile').child(random);
|
||||
|
||||
final UploadTask uploadTask = ref.putFile(image);
|
||||
|
||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
||||
|
||||
photoTemp = ref.fullPath;
|
||||
|
||||
if (snapshot.state == TaskState.success) {
|
||||
return true;
|
||||
Future<void> _loadCurrentUser() async {
|
||||
final currentUser = AuthenticationRepository.instance.currentUser.value;
|
||||
if (currentUser != null) {
|
||||
_nameController.text = currentUser.name;
|
||||
_emailController.text = currentUser.email ?? '';
|
||||
_phoneNumberController.text = currentUser.phoneNumber ?? '';
|
||||
_photoUrl = currentUser.picture;
|
||||
_ciudad = currentUser.city.isNotEmpty ? currentUser.city : '...';
|
||||
_email = currentUser.email;
|
||||
genderDb = currentUser.gender ?? '';
|
||||
birthDateDb = currentUser.birthday ?? '';
|
||||
} else {
|
||||
return false;
|
||||
// Fallback: fetch from API
|
||||
try {
|
||||
final Map<String, dynamic> data =
|
||||
await ApiService.instance.get('/auth/me');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_nameController.text = data['name'] ?? '';
|
||||
_emailController.text = data['email'] ?? '';
|
||||
_phoneNumberController.text = data['phone'] ?? '';
|
||||
_photoUrl = data['picture'];
|
||||
_ciudad = data['city'] ?? '...';
|
||||
_email = data['email'];
|
||||
genderDb = data['gender'] ?? '';
|
||||
birthDateDb = data['birthday'] ?? '';
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading user: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Widget> downloadImage(Reference ref) async {
|
||||
Future<String?> _uploadImageFile(File image) async {
|
||||
try {
|
||||
if (_photo == '...' || _photo.isEmpty) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showChoiceDialog(context);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2BA4EC),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 90,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final imageData = await ref.getData();
|
||||
if (imageData != null) {
|
||||
final widgetImage = GestureDetector(
|
||||
onTap: () {
|
||||
_showChoiceDialog(context);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
||||
child: ClipOval(
|
||||
child: Image.memory(
|
||||
imageData,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return widgetImage;
|
||||
} else {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showChoiceDialog(context);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2BA4EC),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 90,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final uri =
|
||||
Uri.parse('${ApiService.baseUrl}/storage/upload');
|
||||
final request = http.MultipartRequest('POST', uri);
|
||||
final token = await ApiService.instance.getToken();
|
||||
if (token != null) {
|
||||
request.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
request.files.add(await http.MultipartFile.fromPath('file', image.path));
|
||||
final streamed = await request.send();
|
||||
final resp = await http.Response.fromStream(streamed);
|
||||
if (resp.statusCode >= 200 && resp.statusCode < 300) {
|
||||
final json = ApiService.instance.parseJson(resp.body);
|
||||
return json['url'] as String?;
|
||||
}
|
||||
} catch (e) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showChoiceDialog(context);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 50),
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2BA4EC),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 90,
|
||||
),
|
||||
),
|
||||
);
|
||||
print('Error uploading image: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> updateInfo() async {
|
||||
final currentUser = _auth.currentUser;
|
||||
final currentPhoneNumber = _auth.currentUser!.phoneNumber;
|
||||
final Map<String, dynamic> body = {};
|
||||
|
||||
String newName = _nameController.text.trim();
|
||||
String newEmail = _emailController.text.trim();
|
||||
String newPassword = _passwordController.text.trim();
|
||||
|
||||
if (gender.isNotEmpty) {
|
||||
try {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.set({'gender': gender}, SetOptions(merge: true));
|
||||
|
||||
genderDb = gender;
|
||||
} catch (e) {
|
||||
print('Error al actualizar el genero: $e');
|
||||
}
|
||||
}
|
||||
|
||||
if (birthDate != null) {
|
||||
try {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.set({'birth_date': birthDate.toString()}, SetOptions(merge: true));
|
||||
|
||||
birthDateDb = birthDate.toString();
|
||||
} catch (e) {
|
||||
print('Error al actualizar la fecha de nacimiento: $e');
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
|
||||
if (newName.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Nombre Invalido',
|
||||
'Ingresa un nombre válido.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableLoginWithEmail) {
|
||||
if (newEmail.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Correo Invalido',
|
||||
'Ingresa un email válido.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUser?.displayName != newName) {
|
||||
try {
|
||||
await FirebaseAuth.instance.currentUser!.updateDisplayName(newName);
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.update({'name': newName, 'lowerName': newName.toLowerCase()});
|
||||
} catch (e) {
|
||||
print('Error al actualizar el nombre: $e');
|
||||
}
|
||||
}
|
||||
|
||||
String? selectedCity = _ciudad;
|
||||
|
||||
if (kIsWeb) {
|
||||
try {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.update({'city': selectedCity});
|
||||
} catch (e) {
|
||||
print('Error al actualizar la ciudad: $e');
|
||||
}
|
||||
}
|
||||
|
||||
if (enableLoginWithEmail) {
|
||||
if (currentUser?.email != newEmail) {
|
||||
if (newPassword.isNotEmpty) {
|
||||
bool updateEmailSuccess =
|
||||
await updateEmailAndPassword(newEmail, newPassword);
|
||||
|
||||
if (updateEmailSuccess) {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.update({'email': newEmail});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Contraseña Invalida',
|
||||
'Por favor ingresa una contraseña.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
final newName = _nameController.text.trim();
|
||||
if (newName.isNotEmpty) body['name'] = newName;
|
||||
if (_ciudad != '...' && _ciudad.isNotEmpty) body['city'] = _ciudad;
|
||||
if (gender.isNotEmpty) body['gender'] = gender;
|
||||
if (birthDate != null) body['birthday'] = birthDate.toString();
|
||||
|
||||
try {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.update({'phoneNumber': currentPhoneNumber});
|
||||
} catch (e) {
|
||||
print('$e');
|
||||
}
|
||||
|
||||
try {
|
||||
if (imagen_to_upload == null) {
|
||||
} else {
|
||||
final uploaded = await uploadImage(imagen_to_upload!);
|
||||
updateImage(photoTemp);
|
||||
if (imagen_to_upload != null) {
|
||||
final url = await _uploadImageFile(imagen_to_upload!);
|
||||
if (url != null) body['picture'] = url;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error al actualizar la imagen de perfil $e');
|
||||
}
|
||||
|
||||
WarningSnackbar.show(
|
||||
title: 'Informacion actualizada',
|
||||
message: 'Tu informacion ha sido actualizada con exito.',
|
||||
icon: const Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateEmailAndPassword(
|
||||
String newEmail, String currentPassword) async {
|
||||
final user = _auth.currentUser;
|
||||
|
||||
if (user!.email! == newEmail) {
|
||||
Get.snackbar(
|
||||
'No se puede actualizar',
|
||||
'El correo actual no puede ser actualizado.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newEmail.contains('@') || !newEmail.contains('.')) {
|
||||
Get.snackbar(
|
||||
'No se puede actualizar',
|
||||
'Ingresa un correo electrónico valido.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final emailExistsQuery = await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.where('email', isEqualTo: newEmail)
|
||||
.get();
|
||||
|
||||
if (emailExistsQuery.docs.isNotEmpty) {
|
||||
Get.snackbar(
|
||||
'No se puede actualizar',
|
||||
'El nuevo correo electrónico ya está en uso.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final credential = EmailAuthProvider.credential(
|
||||
email: user.email!, password: currentPassword);
|
||||
await user.reauthenticateWithCredential(credential);
|
||||
|
||||
await user.updateEmail(newEmail);
|
||||
|
||||
await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(uid)
|
||||
.update({'email': newEmail});
|
||||
|
||||
setState(() {
|
||||
_email = newEmail;
|
||||
});
|
||||
await ApiService.instance.patch('/users/me', body);
|
||||
|
||||
WarningSnackbar.show(
|
||||
title: 'Actualizado exitosamente',
|
||||
message: 'Correo electronico actualizado correctamente.',
|
||||
title: 'Informacion actualizada',
|
||||
message: 'Tu informacion ha sido actualizada con exito.',
|
||||
icon: const Icon(Icons.check, color: Colors.white),
|
||||
backgroundColor: Colors.green,
|
||||
);
|
||||
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
WarningSnackbar.show(
|
||||
title: 'No se pudo actualizar el correo',
|
||||
message:
|
||||
'Verifica tu contraseña actual y asegúrate de que el nuevo correo electrónico no se haya utilizado previamente.',
|
||||
);
|
||||
print('Error updating info: $e');
|
||||
Get.snackbar('Error', 'No se pudo actualizar la información.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showEmailUpdateDialog(BuildContext context) async {
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Actualizar Email'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: emailController,
|
||||
decoration: const InputDecoration(labelText: 'Nuevo Email'),
|
||||
),
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Contraseña Actual'),
|
||||
obscureText: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Cancelar',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
String newEmail = emailController.text.trim();
|
||||
String currentPassword = passwordController.text.trim();
|
||||
if (newEmail.isNotEmpty && currentPassword.isNotEmpty) {
|
||||
_updateEmailAndPassword(newEmail, currentPassword);
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
'Guardar',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> updateEmailAndPassword(String email, String password) async {
|
||||
final User? user = FirebaseAuth.instance.currentUser;
|
||||
if (user != null) {
|
||||
try {
|
||||
await user.updateEmail(email);
|
||||
await user.updatePassword(password);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
WarningSnackbar.show(
|
||||
title: 'Inicia sesión de nuevo',
|
||||
message: 'Inicia la sesión de nuevo para guardar los cambios.',
|
||||
);
|
||||
AuthenticationRepository.instance.logout(uid!);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enableLoginWithEmail = false;
|
||||
|
||||
Future<List<City>> _getCities() async {
|
||||
List<City> citys = [];
|
||||
if (kIsWeb) {
|
||||
try {
|
||||
QuerySnapshot countries = await countriesCollection.get();
|
||||
for (DocumentSnapshot country in countries.docs) {
|
||||
String countryName = country.id;
|
||||
Map<String, dynamic> data = country.data() as Map<String, dynamic>;
|
||||
Map<String, Map<String, String>> states = {};
|
||||
|
||||
for (var entry in data.entries) {
|
||||
String key = entry.key;
|
||||
Map<String, String> cityData =
|
||||
Map<String, String>.from(entry.value);
|
||||
states[key] = cityData;
|
||||
}
|
||||
|
||||
for (var state in states.entries) {
|
||||
var citysState = state.value.entries.map((city) => City(
|
||||
cityName: city.key,
|
||||
coordsOfCity: city.value,
|
||||
stateOfCity: state.key,
|
||||
countryOfCity: countryName,
|
||||
));
|
||||
|
||||
citys.addAll(citysState);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error obteniendo las ciudades: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return citys;
|
||||
}
|
||||
|
||||
Future<void> _showChoiceDialog(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
@@ -607,14 +193,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String city = _ciudad.toString();
|
||||
|
||||
final userProvider = Provider.of<UserProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: PopAppbar(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onPressed: () => Navigator.pop(context),
|
||||
label: 'Perfil',
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
@@ -623,14 +206,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
child: Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_showChoiceDialog(context);
|
||||
},
|
||||
onTap: () => _showChoiceDialog(context),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: (imagen_to_upload != null)
|
||||
? LocalPhoto(file: imagen_to_upload!)
|
||||
: ReferencePhoto(ref: storage.ref().child(_photo))),
|
||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: imagen_to_upload != null
|
||||
? LocalPhoto(file: imagen_to_upload!)
|
||||
: ReferencePhoto(ref: _photoUrl),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 300,
|
||||
@@ -660,71 +242,35 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(),
|
||||
kIsWeb
|
||||
? FutureBuilder<List<City>>(
|
||||
future: _getCities(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return const Center(
|
||||
child:
|
||||
Text('Error al obtener las ciudades'),
|
||||
);
|
||||
} else {
|
||||
List<City> filteredCities = snapshot.data!;
|
||||
|
||||
return DropdownButtonFormField<String>(
|
||||
value: _ciudad,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_ciudad = newValue!;
|
||||
});
|
||||
},
|
||||
items: filteredCities.map((City city) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: city.cityName,
|
||||
child: Text(city.cityName ?? ''),
|
||||
);
|
||||
}).toList(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.near_me),
|
||||
hintText: _ciudad,
|
||||
),
|
||||
);
|
||||
}
|
||||
TextFormField(
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final String? ciudad = await Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const CityScreen();
|
||||
},
|
||||
)
|
||||
: TextFormField(
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final String? ciudad = await Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const CityScreen();
|
||||
},
|
||||
),
|
||||
) as String?;
|
||||
|
||||
if (ciudad != null) {
|
||||
setState(() {
|
||||
_ciudad = ciudad;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.near_me),
|
||||
suffixIcon: const Icon(Icons.arrow_drop_down),
|
||||
hintStyle: city == ''
|
||||
? const TextStyle()
|
||||
: const TextStyle(color: Colors.black87),
|
||||
hintText: city == '' ? 'Ciudad' : city,
|
||||
),
|
||||
),
|
||||
) as String?;
|
||||
|
||||
if (ciudad != null) {
|
||||
setState(() {
|
||||
_ciudad = ciudad;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.near_me),
|
||||
suffixIcon: const Icon(Icons.arrow_drop_down),
|
||||
hintStyle: city == ''
|
||||
? const TextStyle()
|
||||
: const TextStyle(color: Colors.black87),
|
||||
hintText: city == '' || city == '...'
|
||||
? 'Ciudad'
|
||||
: city,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
TextFormField(
|
||||
controller: _phoneNumberController,
|
||||
@@ -778,152 +324,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
: const SizedBox(),
|
||||
_email != null && _email != ''
|
||||
? TextFormField(
|
||||
onTap: () {
|
||||
_showEmailUpdateDialog(context);
|
||||
},
|
||||
readOnly: true,
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.email_outlined),
|
||||
hintText: 'Email (Obligatorio)',
|
||||
hintText: 'Email',
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
const SizedBox(height: 20),
|
||||
_email == null || _email == ''
|
||||
? PrimaryCheckbox(
|
||||
text:
|
||||
'Habilitar inicio de sesión con correo (Opcional)',
|
||||
initialValue: enableLoginWithEmail,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
enableLoginWithEmail = value;
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
const SizedBox(height: 15),
|
||||
enableLoginWithEmail
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.blue,
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
validator: (String? value) {
|
||||
if (enableLoginWithEmail) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Por favor ingrese un email';
|
||||
}
|
||||
final RegExp emailRegExp = RegExp(
|
||||
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
if (!emailRegExp.hasMatch(value)) {
|
||||
return 'Por favor ingrese un email válido';
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.email_outlined),
|
||||
hintText: 'Email',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
_email != null && _email != ''
|
||||
? const SizedBox.shrink()
|
||||
: TextFormField(
|
||||
controller: _passwordController,
|
||||
obscureText: _obscureText,
|
||||
validator: (value) {
|
||||
if (enableLoginWithEmail) {
|
||||
if (value == null ||
|
||||
value.isEmpty) {
|
||||
return 'Por favor ingrese una contraseña.';
|
||||
}
|
||||
if (value.length < 5) {
|
||||
return 'Debe tener al menos 5 caracteres.';
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.lock_outline),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscureText
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscureText =
|
||||
!_obscureText;
|
||||
});
|
||||
},
|
||||
),
|
||||
hintText: 'Contraseña'),
|
||||
),
|
||||
_email != null && _email != ''
|
||||
? const SizedBox.shrink()
|
||||
: const SizedBox(height: 20),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
top: 5,
|
||||
bottom: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD6F4FF),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(1, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 20,
|
||||
color: Colors.black54,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Al habilitar el inicio de sesión con correo, se cerrara la sesión actual.',
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -955,12 +364,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
PrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
await updateInfo();
|
||||
if (kIsWeb) {
|
||||
await updateInfo().whenComplete(() {
|
||||
html.window.location.reload();
|
||||
});
|
||||
} else {
|
||||
await updateInfo();
|
||||
html.window.location.reload();
|
||||
}
|
||||
}
|
||||
await userProvider.updateUserDataAndScores();
|
||||
|
||||
Reference in New Issue
Block a user