update
This commit is contained in:
+76
-1
@@ -1 +1,76 @@
|
|||||||
const String apiKey = 'AIzaSyBqZNw7kj3pEYH-pusqTMxXhbja8HR7eOc';
|
const String GOOGLE_MAPS_API_KEY = 'AIzaSyCW_og6qQ8W8G-5_BxIS4sBnl8cLkjL95s';
|
||||||
|
|
||||||
|
const String MAP_STYLE = '''
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"elementType": "labels.icon",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"visibility": "on",
|
||||||
|
"color": "#6F6F6F"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"elementType": "labels.text.fill",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#616161"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"elementType": "labels.text.stroke",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#f5f5f5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"featureType": "administrative.land_parcel",
|
||||||
|
"elementType": "labels.text.fill",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#bdbdbd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"featureType": "poi",
|
||||||
|
"elementType": "geometry",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#eeeeee"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"featureType": "poi",
|
||||||
|
"elementType": "labels.text.fill",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#757575"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"featureType": "poi.park",
|
||||||
|
"elementType": "geometry",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#e5e5e5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"featureType": "poi.park",
|
||||||
|
"elementType": "labels.text.fill",
|
||||||
|
"stylers": [
|
||||||
|
{
|
||||||
|
"color": "#9e9e9e"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
''';
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ import 'package:prosappco/src/presentation/screens/profile/profile_pro.dart';
|
|||||||
import 'package:prosappco/src/presentation/screens/register/register.dart';
|
import 'package:prosappco/src/presentation/screens/register/register.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/request_sent.dart';
|
import 'package:prosappco/src/presentation/screens/request_sent.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart';
|
import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/service.dart';
|
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/solicitudes.dart';
|
import 'package:prosappco/src/presentation/screens/solicitudes.dart';
|
||||||
import 'package:prosappco/src/services/local_notifications.dart';
|
import 'package:prosappco/src/services/local_notifications.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import 'package:get/get.dart';
|
|||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:google_sign_in/google_sign_in.dart';
|
||||||
import 'package:prosappco/src/authentication/exceptions/register_failed.dart';
|
import 'package:prosappco/src/authentication/exceptions/register_failed.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/login/login.dart';
|
import 'package:prosappco/src/presentation/screens/login/login.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/service.dart';
|
// import 'package:prosappco/src/presentation/screens/service.dart';
|
||||||
|
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
||||||
|
|
||||||
class AuthenticationRepository extends GetxController {
|
class AuthenticationRepository extends GetxController {
|
||||||
static AuthenticationRepository get instance => Get.find();
|
static AuthenticationRepository get instance => Get.find();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
import 'package:prosappco/src/authentication/authentication_repository.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/service.dart';
|
// import 'package:prosappco/src/presentation/screens/service.dart';
|
||||||
|
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
||||||
|
|
||||||
class OTPController extends GetxController {
|
class OTPController extends GetxController {
|
||||||
static OTPController get instance => Get.find();
|
static OTPController get instance => Get.find();
|
||||||
|
|||||||
@@ -29,30 +29,55 @@ class SettingModel {
|
|||||||
this.terminosCondiciones,
|
this.terminosCondiciones,
|
||||||
);
|
);
|
||||||
|
|
||||||
static Future<SettingModel> fromJson(Map<String, dynamic> json) async {
|
static Future<SettingModel> fromJson(Map<String, dynamic>? json) async {
|
||||||
try {
|
try {
|
||||||
if (json == null)
|
if (json == null) {
|
||||||
return SettingModel(
|
return SettingModel(
|
||||||
false, false, false, '', '', '', '', '', '', '', '', '');
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SettingModel(
|
return SettingModel(
|
||||||
json['domicilio'],
|
json['domicilios'] ?? false,
|
||||||
json['google'],
|
json['google'] ?? false,
|
||||||
json['tarifa'],
|
json['tarifas'] ?? false,
|
||||||
json['titulo_soporte'],
|
json['titulo_soporte'] ?? '',
|
||||||
json['parrafo_soporte'],
|
json['parrafo_soporte'] ?? '',
|
||||||
json['numero_soporte'],
|
json['numero_soporte'] ?? '',
|
||||||
json['email_soporte'],
|
json['email_soporte'] ?? '',
|
||||||
json['dias_soporte'],
|
json['dias_soporte'] ?? '',
|
||||||
json['horas_soporte'],
|
json['horas_soporte'] ?? '',
|
||||||
json['version'],
|
json['version'] ?? '', // Asegúrate de manejar nulos aquí
|
||||||
json['politicas_privacidad'],
|
json['politicas_privacidad'] ?? '',
|
||||||
json['terminos_condiciones'],
|
json['terminos_condiciones'] ?? '',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error settings: $e');
|
print('Error settings: $e');
|
||||||
return SettingModel(
|
return SettingModel(
|
||||||
false, false, false, '', '', '', '', '', '', '', '', '');
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import 'package:prosappco/src/authentication/authentication_repository.dart';
|
|||||||
import 'package:prosappco/src/components/photo_view.dart';
|
import 'package:prosappco/src/components/photo_view.dart';
|
||||||
import 'package:prosappco/src/components/pop_appbar.dart';
|
import 'package:prosappco/src/components/pop_appbar.dart';
|
||||||
import 'package:prosappco/src/controllers/info_%20professional.dart';
|
import 'package:prosappco/src/controllers/info_%20professional.dart';
|
||||||
|
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
||||||
import 'package:prosappco/src/services/select_image_profile.dart';
|
import 'package:prosappco/src/services/select_image_profile.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
Reference ref =
|
Reference ref =
|
||||||
storage.ref().child('users').child(uid!).child('cedula').child(random);
|
storage.ref().child('users').child(uid!).child('cedula').child(random);
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
final UploadTask uploadTask = ref.putFile(image, metadata);
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
||||||
|
|
||||||
@@ -217,6 +218,10 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final metadata = SettableMetadata(
|
||||||
|
contentType: 'application/pdf',
|
||||||
|
);
|
||||||
|
|
||||||
Future<bool> uploadCertificado(File image) async {
|
Future<bool> uploadCertificado(File image) async {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
final formattedDate = DateFormat('HHmmssddMMyyyy').format(now);
|
||||||
@@ -230,7 +235,7 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
.child('certificado_profesional')
|
.child('certificado_profesional')
|
||||||
.child(random);
|
.child(random);
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
final UploadTask uploadTask = ref.putFile(image, metadata);
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
||||||
|
|
||||||
@@ -281,24 +286,31 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> uploadImage(File image) async {
|
Future<bool> uploadImage(File image) async {
|
||||||
final String namefile = image.path.split('/').last;
|
try {
|
||||||
|
final String namefile = image.path.split('/').last;
|
||||||
|
|
||||||
Reference ref = storage
|
Reference ref = storage
|
||||||
.ref()
|
.ref()
|
||||||
.child('users')
|
.child('users')
|
||||||
.child(uid!)
|
.child(uid!)
|
||||||
.child('profile')
|
.child('profile')
|
||||||
.child(namefile);
|
.child(namefile);
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
final UploadTask uploadTask = ref.putFile(image);
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
final TaskSnapshot snapshot = await uploadTask;
|
||||||
|
|
||||||
photoTemp = ref.fullPath;
|
if (snapshot.state == TaskState.success) {
|
||||||
|
// Obtén la URL de descarga de la imagen y actualiza en Firestore
|
||||||
|
String downloadURL = await ref.getDownloadURL();
|
||||||
|
await updateImage(downloadURL);
|
||||||
|
|
||||||
if (snapshot.state == TaskState.success) {
|
return true;
|
||||||
return true;
|
} else {
|
||||||
} else {
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error al cargar la imagen: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +373,7 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
.child('especializaciones')
|
.child('especializaciones')
|
||||||
.child('e${DateTime.now().millisecondsSinceEpoch}.pdf');
|
.child('e${DateTime.now().millisecondsSinceEpoch}.pdf');
|
||||||
|
|
||||||
final UploadTask uploadTask = ref.putFile(image);
|
final UploadTask uploadTask = ref.putFile(image, metadata);
|
||||||
|
|
||||||
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true);
|
||||||
|
|
||||||
@@ -419,22 +431,47 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
especializacion.split(',').map((e) => e.trim()).toList();
|
especializacion.split(',').map((e) => e.trim()).toList();
|
||||||
|
|
||||||
if (cedula.isEmpty) {
|
if (cedula.isEmpty) {
|
||||||
_showCustomSnackBar(
|
WarningSnackbar.show(
|
||||||
context, 'Por favor, adjunta el documento PDF de tu cédula.');
|
title: 'Te faltan campos!!',
|
||||||
|
message: 'Por favor, ingresa tu cedula',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image_cedula == null) {
|
if (image_cedula == null) {
|
||||||
_showCustomSnackBar(
|
WarningSnackbar.show(
|
||||||
context, 'Por favor, adjunte el documento PDF de su cédula.');
|
title: 'Te faltan archivos!!',
|
||||||
|
message: 'Por favor, adjunta el documento PDF de tu cedula',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_profession.isEmpty) {
|
||||||
|
WarningSnackbar.show(
|
||||||
|
title: 'Te falta elegir una profesión!!',
|
||||||
|
message:
|
||||||
|
'Por favor, elige tu profesión antes de enviar la información.',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image_certificado == null) {
|
if (image_certificado == null) {
|
||||||
_showCustomSnackBar(context,
|
WarningSnackbar.show(
|
||||||
'Por favor, adjunta el documento PDF de tu certificado. ¡Gracias por tu colaboración!');
|
title: 'Te faltan archivos!!',
|
||||||
|
message: 'Por favor, adjunta el documento PDF de tu certificado',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (imagen_to_upload == null) {
|
||||||
|
WarningSnackbar.show(
|
||||||
|
title: 'Sube una foto de perfil',
|
||||||
|
message: 'Para continuar debes subir una imagen de perfil',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Actualiza la imagen de perfil si hay cambios
|
||||||
|
updateImage(photoTemp);
|
||||||
|
}
|
||||||
|
|
||||||
// Actualiza los datos del usuario en Firestore
|
// Actualiza los datos del usuario en Firestore
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
||||||
@@ -448,11 +485,6 @@ class ProfessionalProfileScreenState extends State<ProfessionalProfileScreen> {
|
|||||||
uploadCertificado(image_certificado!);
|
uploadCertificado(image_certificado!);
|
||||||
uploadEspecializaciones(images_especializacion);
|
uploadEspecializaciones(images_especializacion);
|
||||||
|
|
||||||
// Actualiza la imagen de perfil si hay cambios
|
|
||||||
if (imagen_to_upload != null) {
|
|
||||||
updateImage(photoTemp);
|
|
||||||
}
|
|
||||||
// Navega a la siguiente pantalla
|
|
||||||
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
|
Navigator.pushReplacementNamed(context, '/solicitudEnviada');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ import 'package:prosappco/src/presentation/widgets/profile/birth_date_picker.dar
|
|||||||
import 'package:prosappco/src/presentation/screens/city.dart';
|
import 'package:prosappco/src/presentation/screens/city.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/new_number.dart';
|
import 'package:prosappco/src/presentation/screens/new_number.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/new_password.dart';
|
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/select_image_profile.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/profile/gender_dropdown.dart';
|
||||||
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import '../../../components/photo_view.dart';
|
import '../../../components/photo_view.dart';
|
||||||
|
|
||||||
class ProfileScreen extends StatefulWidget {
|
class ProfileScreen extends StatefulWidget {
|
||||||
@@ -275,13 +279,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newEmail.isEmpty) {
|
if (enableLoginWithEmail) {
|
||||||
Get.snackbar(
|
if (newEmail.isEmpty) {
|
||||||
'Correo Invalido',
|
Get.snackbar(
|
||||||
'Ingresa un email válido.',
|
'Correo Invalido',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
'Ingresa un email válido.',
|
||||||
);
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
return;
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser?.displayName != newName) {
|
if (currentUser?.displayName != newName) {
|
||||||
@@ -296,18 +302,29 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser?.email != newEmail) {
|
if (enableLoginWithEmail) {
|
||||||
if (newPassword.isNotEmpty) {
|
if (currentUser?.email != newEmail) {
|
||||||
await FirebaseFirestore.instance.collection('users').doc(uid).update({
|
if (newPassword.isNotEmpty) {
|
||||||
'email': newEmail,
|
bool updateEmailSuccess =
|
||||||
});
|
await updateEmailAndPassword(newEmail, newPassword);
|
||||||
updateEmailAndPassword(newEmail, newPassword);
|
|
||||||
} else {
|
if (updateEmailSuccess) {
|
||||||
Get.snackbar(
|
await FirebaseFirestore.instance
|
||||||
'Contraseña Invalida',
|
.collection('users')
|
||||||
'Porfavor ingresa una contraseña.',
|
.doc(uid)
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
.update({
|
||||||
);
|
'email': newEmail,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Get.snackbar(
|
||||||
|
'Contraseña Invalida',
|
||||||
|
'Por favor ingresa una contraseña.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,18 +337,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
print('e');
|
print('e');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.collection('users')
|
|
||||||
.doc(uid)
|
|
||||||
.update({'email': newEmail});
|
|
||||||
} catch (e) {
|
|
||||||
print('Error al actualizar la imagen de perfil $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (imagen_to_upload == null) {
|
if (imagen_to_upload == null) {
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
final uploaded = await uploadImage(imagen_to_upload!);
|
final uploaded = await uploadImage(imagen_to_upload!);
|
||||||
updateImage(photoTemp);
|
updateImage(photoTemp);
|
||||||
@@ -341,10 +348,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
print('Error al actualizar la imagen de perfil $e');
|
print('Error al actualizar la imagen de perfil $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
Get.snackbar(
|
WarningSnackbar.show(
|
||||||
'Informacion actualizada',
|
title: 'Informacion actualizada',
|
||||||
'Tu informacion ha sido actualizada con exito.',
|
message: 'Tu informacion ha sido actualizada con exito.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
icon: const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,19 +412,23 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
_email = newEmail;
|
_email = newEmail;
|
||||||
});
|
});
|
||||||
|
|
||||||
Get.snackbar(
|
WarningSnackbar.show(
|
||||||
'Éxito',
|
title: 'Actualizado exitosamente',
|
||||||
'Correo electrónico actualizado correctamente.',
|
message: 'Correo electronico actualizado correctamente.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
icon: const Icon(Icons.check, color: Colors.white),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
);
|
);
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar(
|
WarningSnackbar.show(
|
||||||
'No se pudo actualizar el correo',
|
title: 'No se pudo actualizar el correo',
|
||||||
'Verifica tu contraseña actual y asegúrate de que el nuevo correo electrónico no se haya utilizado previamente.',
|
message:
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
'Verifica tu contraseña actual y asegúrate de que el nuevo correo electrónico no se haya utilizado previamente.',
|
||||||
);
|
);
|
||||||
|
|
||||||
print('Error al actualizar el correo electrónico: $e');
|
print('Error al actualizar el correo electrónico: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,23 +487,26 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateEmailAndPassword(String email, String password) async {
|
Future<bool> updateEmailAndPassword(String email, String password) async {
|
||||||
final User? user = FirebaseAuth.instance.currentUser;
|
final User? user = FirebaseAuth.instance.currentUser;
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
try {
|
try {
|
||||||
await user.updateEmail(email);
|
await user.updateEmail(email);
|
||||||
await user.updatePassword(password);
|
await user.updatePassword(password);
|
||||||
|
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar(
|
WarningSnackbar.show(
|
||||||
'Agregar correo',
|
title: 'Inicia sesión de nuevo',
|
||||||
'Inicia sesión para asegurarnos de que seas tú.',
|
message:
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
'Para agregar un correo debes haber iniciado sesión recientemente.');
|
||||||
);
|
|
||||||
// AuthenticationRepository.instance.logout(uid!);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool enableLoginWithEmail = false;
|
||||||
|
|
||||||
Future<void> _showChoiceDialog(BuildContext context) async {
|
Future<void> _showChoiceDialog(BuildContext context) async {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -538,6 +556,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String city = _ciudad.toString();
|
String city = _ciudad.toString();
|
||||||
|
|
||||||
|
final userProvider = Provider.of<UserProvider>(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PopAppbar(
|
appBar: PopAppbar(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -582,70 +602,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
prefixIcon: Icon(Icons.person_outline),
|
prefixIcon: Icon(Icons.person_outline),
|
||||||
hintText: 'Nombre (Obligatorio)'),
|
hintText: 'Nombre (Obligatorio)'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 0),
|
const SizedBox(),
|
||||||
_email != null
|
|
||||||
? TextFormField(
|
|
||||||
onTap: () {
|
|
||||||
_showEmailUpdateDialog(context);
|
|
||||||
},
|
|
||||||
readOnly: true,
|
|
||||||
controller: _emailController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
prefixIcon: Icon(Icons.email_outlined),
|
|
||||||
hintText: 'Email (Obligatorio)'),
|
|
||||||
)
|
|
||||||
: TextFormField(
|
|
||||||
controller: _emailController,
|
|
||||||
validator: (String? value) {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
prefixIcon: Icon(Icons.email_outlined),
|
|
||||||
hintText: 'Email (Obligatorio)'),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20.0),
|
|
||||||
_email != null
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: TextFormField(
|
|
||||||
controller: _passwordController,
|
|
||||||
obscureText: _obscureText,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Porfavor ingrese una contraseña.';
|
|
||||||
}
|
|
||||||
if (value.length < 5) {
|
|
||||||
return 'Debe tener al menos 5 caracteres.';
|
|
||||||
}
|
|
||||||
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 (Obligatorio)'),
|
|
||||||
),
|
|
||||||
_email != null
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: const SizedBox(height: 20.0),
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@@ -719,6 +676,111 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
|
_email != null
|
||||||
|
? TextFormField(
|
||||||
|
onTap: () {
|
||||||
|
_showEmailUpdateDialog(context);
|
||||||
|
},
|
||||||
|
readOnly: true,
|
||||||
|
controller: _emailController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.email_outlined),
|
||||||
|
hintText: 'Email (Obligatorio)'),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_email == null
|
||||||
|
? 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
|
||||||
|
? 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
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -752,6 +814,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
await updateInfo();
|
await updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await userProvider.updateUserDataAndScores();
|
||||||
},
|
},
|
||||||
text: 'Guardar',
|
text: 'Guardar',
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:prosappco/src/models/event_model.dart';
|
|||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
import 'package:prosappco/src/models/setting_model.dart';
|
import 'package:prosappco/src/models/setting_model.dart';
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
import 'package:prosappco/src/models/user_model.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/service.dart';
|
import 'package:prosappco/src/presentation/screens/map/service.dart';
|
||||||
|
|
||||||
class ServiceAfterScreen extends StatefulWidget {
|
class ServiceAfterScreen extends StatefulWidget {
|
||||||
var eventoId;
|
var eventoId;
|
||||||
|
|||||||
+7
-27
@@ -30,14 +30,14 @@ import 'package:prosappco/src/presentation/widgets/shared/primary_button.dart';
|
|||||||
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ServiceScreen extends StatefulWidget {
|
class ServiceOldScreen extends StatefulWidget {
|
||||||
const ServiceScreen({super.key});
|
const ServiceOldScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ServiceScreen> createState() => _ServiceScreenState();
|
State<ServiceOldScreen> createState() => _ServiceOldScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ServiceScreenState extends State<ServiceScreen> {
|
class _ServiceOldScreenState extends State<ServiceOldScreen> {
|
||||||
late String appVersion;
|
late String appVersion;
|
||||||
final Uri _url = Uri.parse(
|
final Uri _url = Uri.parse(
|
||||||
'https://play.google.com/store/apps/details?id=com.prosapp.prosapp');
|
'https://play.google.com/store/apps/details?id=com.prosapp.prosapp');
|
||||||
@@ -57,27 +57,6 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<List<LatLng>> getRouteCoordinates(
|
|
||||||
// double startLat, double startLng, double endLat, double endLng) async {
|
|
||||||
// List<LatLng> polylineCoordinates = [];
|
|
||||||
|
|
||||||
// PolylinePoints polylinePoints = PolylinePoints();
|
|
||||||
|
|
||||||
// PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
|
|
||||||
// 'AIzaSyCW_og6qQ8W8G-5_BxIS4sBnl8cLkjL95s',
|
|
||||||
// PointLatLng(startLat, startLng),
|
|
||||||
// PointLatLng(endLat, endLng),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (result.points.isNotEmpty) {
|
|
||||||
// result.points.forEach((PointLatLng point) {
|
|
||||||
// polylineCoordinates.add(LatLng(point.latitude, point.longitude));
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return polylineCoordinates;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> sendPushNotification(String pro) async {
|
Future<void> sendPushNotification(String pro) async {
|
||||||
try {
|
try {
|
||||||
http.Response response = await http.post(
|
http.Response response = await http.post(
|
||||||
@@ -1217,9 +1196,10 @@ class _ServiceScreenState extends State<ServiceScreen> {
|
|||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _observacionController,
|
controller: _observacionController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Icon(Icons.message_outlined),
|
prefixIcon: Icon(Icons.message_outlined),
|
||||||
hintText: 'Observaciones'),
|
hintText:
|
||||||
|
'Observaciones ${user?.name} - ${user?.phoneNumber}'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
@@ -6,6 +6,7 @@ import 'package:prosappco/src/authentication/authentication_repository.dart';
|
|||||||
import 'package:prosappco/src/components/photo_view.dart';
|
import 'package:prosappco/src/components/photo_view.dart';
|
||||||
import 'package:prosappco/src/models/scores_model.dart';
|
import 'package:prosappco/src/models/scores_model.dart';
|
||||||
import 'package:prosappco/src/models/user_model.dart';
|
import 'package:prosappco/src/models/user_model.dart';
|
||||||
|
import 'package:prosappco/src/presentation/widgets/shared/warning_snackbar.dart';
|
||||||
import 'package:prosappco/src/providers/user_provider.dart';
|
import 'package:prosappco/src/providers/user_provider.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/configuracion.dart';
|
import 'package:prosappco/src/presentation/screens/configuracion.dart';
|
||||||
import 'package:prosappco/src/presentation/screens/messages_user.dart';
|
import 'package:prosappco/src/presentation/screens/messages_user.dart';
|
||||||
@@ -322,10 +323,10 @@ class DrawerMenu extends StatelessWidget {
|
|||||||
user?.city == '' ||
|
user?.city == '' ||
|
||||||
user?.phoneNumber == '' ||
|
user?.phoneNumber == '' ||
|
||||||
user?.phoneNumber == null) {
|
user?.phoneNumber == null) {
|
||||||
Get.snackbar(
|
WarningSnackbar.show(
|
||||||
'Completa tu perfil',
|
title: 'Completa tu perfil',
|
||||||
'Para ser un profesional registrado, asegúrate de llenar todos los campos necesarios y no olvides guardar tus cambios para que surtan efecto.',
|
message:
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
'Para ser un profesional registrado, asegúrate de llenar todos los campos necesarios y no olvides guardar tus cambios para que surtan efecto.',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (user?.phoneNumber != user?.phoneNumber) {
|
if (user?.phoneNumber != user?.phoneNumber) {
|
||||||
|
|||||||
@@ -35,6 +35,20 @@ class UserProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateUserDataAndScores() async {
|
||||||
|
var uid = FirebaseAuth.instance.currentUser?.uid;
|
||||||
|
if (uid == null) return;
|
||||||
|
|
||||||
|
var documentSnapshot = await _firestore.collection('users').doc(uid).get();
|
||||||
|
|
||||||
|
if (documentSnapshot.exists) {
|
||||||
|
final data = documentSnapshot.data() as Map<String, dynamic>;
|
||||||
|
_user = UserModel.fromFirestore(data);
|
||||||
|
_score = await ScoresModel.scoreTo(uid, false, false);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UserModel? get user => _user;
|
UserModel? get user => _user;
|
||||||
ScoresModel? get score => _score;
|
ScoresModel? get score => _score;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import firebase_messaging
|
|||||||
import firebase_storage
|
import firebase_storage
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import geolocator_apple
|
import geolocator_apple
|
||||||
|
import location
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
@@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||||
|
LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|||||||
@@ -773,6 +773,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
location:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: location
|
||||||
|
sha256: "06be54f682c9073cbfec3899eb9bc8ed90faa0e17735c9d9fa7fe426f5be1dd1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.3"
|
||||||
|
location_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: location_platform_interface
|
||||||
|
sha256: "8aa1d34eeecc979d7c9fe372931d84f6d2ebbd52226a54fe1620de6fdc0753b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
location_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: location_web
|
||||||
|
sha256: ec484c66e8a4ff1ee5d044c203f4b6b71e3a0556a97b739a5bc9616de672412b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+3
-2
@@ -35,6 +35,9 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
shared_preferences: ^2.0.10
|
shared_preferences: ^2.0.10
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
|
location: ^5.0.3
|
||||||
|
flutter_polyline_points: ^2.0.0
|
||||||
|
google_maps_flutter: ^2.5.0
|
||||||
firebase_auth: ^4.6.2
|
firebase_auth: ^4.6.2
|
||||||
firebase_core: ^2.13.1
|
firebase_core: ^2.13.1
|
||||||
file_picker: ^5.3.2
|
file_picker: ^5.3.2
|
||||||
@@ -51,8 +54,6 @@ dependencies:
|
|||||||
flutter_animate:
|
flutter_animate:
|
||||||
flutter_rating_bar:
|
flutter_rating_bar:
|
||||||
table_calendar:
|
table_calendar:
|
||||||
google_maps_flutter: ^2.5.0
|
|
||||||
flutter_polyline_points: ^2.0.0
|
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
geolocator: ^9.0.2
|
geolocator: ^9.0.2
|
||||||
geocoding: ^2.1.0
|
geocoding: ^2.1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user