diff --git a/android/build.gradle b/android/build.gradle index 3593a87..2d4a9ec 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -27,6 +27,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/lib/src/components/drawer_menu.dart b/lib/src/components/drawer_menu.dart index f1dc6bb..5c5b266 100644 --- a/lib/src/components/drawer_menu.dart +++ b/lib/src/components/drawer_menu.dart @@ -1,5 +1,6 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; @@ -8,7 +9,9 @@ import 'package:prosappco/src/models/scores_model.dart'; import 'package:prosappco/src/models/user_model.dart'; import 'package:prosappco/src/screens/configuracion.dart'; import 'package:prosappco/src/screens/messages_user.dart'; +import 'package:prosappco/src/screens/professional_profile_web.dart'; import 'package:prosappco/src/screens/profile.dart'; +import 'package:prosappco/src/screens/profile_web.dart'; import 'package:prosappco/src/screens/reputation.dart'; import 'package:prosappco/src/screens/support.dart'; import 'package:get/get.dart'; @@ -53,14 +56,25 @@ class _DrawerMenuState extends State { children: [ ListTile( onTap: () { - Navigator.push( - context, - CupertinoPageRoute( - builder: (BuildContext context) { - return const ProfileScreen(); - }, - ), - ); + if (kIsWeb) { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ProfileWebScreen(); + }, + ), + ); + } else { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ProfileScreen(); + }, + ), + ); + } }, title: Text(user?.name ?? '', style: const TextStyle(fontWeight: FontWeight.bold)), @@ -129,14 +143,25 @@ class _DrawerMenuState extends State { ), ListTile( onTap: () { - Navigator.push( - context, - CupertinoPageRoute( - builder: (BuildContext context) { - return const ProfileScreen(); - }, - ), - ); + if (kIsWeb) { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ProfileWebScreen(); + }, + ), + ); + } else { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ProfileScreen(); + }, + ), + ); + } }, leading: const Icon( Icons.person_outline, @@ -347,7 +372,16 @@ class _DrawerMenuState extends State { ); } else { if (user?.state == 'pendiente' || user?.state == null) { - Navigator.pushNamed(context, '/profesionalProfile'); + if (kIsWeb) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const ProfessionalProfileWebScreen()), + ); + } else { + Navigator.pushNamed(context, '/profesionalProfile'); + } } else if (user?.state == 'revision') { Navigator.pushNamed(context, '/profesionalRevision'); } else if (user?.state == 'activo') { diff --git a/lib/src/components/photo_view_web.dart b/lib/src/components/photo_view_web.dart new file mode 100644 index 0000000..b814c91 --- /dev/null +++ b/lib/src/components/photo_view_web.dart @@ -0,0 +1,118 @@ +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; + +const double photoSize = 100; +const double iconSize = 55; + +class ReferencePhotoWeb extends StatelessWidget { + Reference? ref; + double size; + double sizeIcon; + double sizeCircle; + + ReferencePhotoWeb({ + super.key, + required this.ref, + this.sizeIcon = iconSize, + this.size = photoSize, + this.sizeCircle = photoSize, + }); + + Future downloadImage() async { + try { + if (ref != null) { + final imageData = await ref!.getData(); + if (imageData != null) { + return ClipOval( + child: Image.memory( + imageData, + width: size, + height: size, + fit: BoxFit.cover, + ), + ); + } + } + // ignore: empty_catches + } catch (e) {} + + return DefaultPhotoWeb( + sizeDefault: sizeCircle, + iconDefault: sizeIcon, + ); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: downloadImage(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + // mientras la llamada asíncrona está en proceso, muestra un mensaje de carga + return SizedBox( + width: size, + height: size, + child: const Center( + child: CircularProgressIndicator(), + ), + ); + } else if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + return snapshot.data!; + } else { + return DefaultPhotoWeb(); + } + }); + } +} + +class DefaultPhotoWeb extends StatelessWidget { + double sizeDefault; + double iconDefault; + + DefaultPhotoWeb({ + super.key, + this.sizeDefault = photoSize, + this.iconDefault = iconSize, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: sizeDefault, + height: sizeDefault, + decoration: BoxDecoration( + color: const Color(0xFF2BA4EC), + borderRadius: BorderRadius.circular(50), + ), + child: Icon( + Icons.person, + color: const Color.fromARGB(255, 255, 255, 255), + size: iconDefault, + ), + ).animate().shake(); + } +} + +class LocalPhotoWeb extends StatelessWidget { + Uint8List? file; + LocalPhotoWeb({super.key, required this.file}); + + @override + Widget build(BuildContext context) { + if (file == null) { + return DefaultPhotoWeb(); + } else { + return ClipOval( + child: Image.memory( + file!, + width: photoSize, + height: photoSize, + fit: BoxFit.cover, + ), + ); + } + } +} diff --git a/lib/src/screens/new_number.dart b/lib/src/screens/new_number.dart index f6a916e..0415a61 100644 --- a/lib/src/screens/new_number.dart +++ b/lib/src/screens/new_number.dart @@ -1,4 +1,5 @@ import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl_phone_field/intl_phone_field.dart'; @@ -33,87 +34,179 @@ class _NewNumberScreenState extends State { @override Widget build(BuildContext context) { return SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - backgroundColor: Colors.white, - iconTheme: const IconThemeData( - color: Colors.black, - ), - title: const Text( - 'Añadir numero', - style: TextStyle( + child: Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + backgroundColor: Colors.white, + iconTheme: const IconThemeData( color: Colors.black, ), - ), - ), - body: Container( - padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20), - margin: const EdgeInsets.only(top: 30, left: 50, right: 50), - child: Column(children: [ - const Padding( - padding: EdgeInsets.only(bottom: 5), - child: Align( - alignment: Alignment.topLeft, - child: Text('Numero de celular', - style: TextStyle(fontSize: 18.0, color: Color(0xFF65676B))), - )), - Form( - key: _formKey, - child: Padding( - padding: const EdgeInsets.only(bottom: 5), - child: IntlPhoneField( - controller: controller.newPhoneNo, - initialCountryCode: 'CO', - onChanged: (newPhoneNo) { - completePhoneNumber = newPhoneNo.completeNumber; - }, - decoration: const InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide(color: Color(0xFFECECEC)), - borderRadius: BorderRadius.all( - Radius.circular(50), - )), - errorBorder: OutlineInputBorder( - borderSide: - BorderSide(color: Color.fromARGB(255, 184, 0, 0)), - borderRadius: BorderRadius.all( - Radius.circular(50), - )), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Color(0xFFECECEC)), - borderRadius: BorderRadius.all( - Radius.circular(50), - )), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: Color(0xFFECECEC)), - borderRadius: BorderRadius.all( - Radius.circular(50), - )), - fillColor: Color.fromARGB(255, 239, 239, 239), - filled: true, - ), - ), + title: const Text( + 'Añadir numero', + style: TextStyle( + color: Colors.black, ), ), - const Padding( - padding: EdgeInsets.only(bottom: 30), - child: Text('Un código será enviado a este numero de celular.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 13.0, color: Color(0xFF65676B))), - ), - Padding( - padding: const EdgeInsets.only(bottom: 30), - child: Center( - child: PrimaryButtom( - onPressed: () { - controller - .updatePhoneNumber(completePhoneNumber.toString()); - }, - label: 'Enviar código')), - ), - ]), + ), + body: !kIsWeb + ? Container( + padding: + const EdgeInsets.symmetric(horizontal: 0, vertical: 20), + margin: const EdgeInsets.only(top: 30, left: 50, right: 50), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(bottom: 5), + child: Align( + alignment: Alignment.topLeft, + child: Text('Numero de celular', + style: TextStyle( + fontSize: 18.0, color: Color(0xFF65676B))), + )), + Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.only(bottom: 5), + child: IntlPhoneField( + controller: controller.newPhoneNo, + initialCountryCode: 'CO', + onChanged: (newPhoneNo) { + completePhoneNumber = newPhoneNo.completeNumber; + }, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: + BorderSide(color: Color(0xFFECECEC)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color.fromARGB(255, 184, 0, 0)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Color(0xFFECECEC)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Color(0xFFECECEC)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + fillColor: Color.fromARGB(255, 239, 239, 239), + filled: true, + ), + ), + ), + ), + const Padding( + padding: EdgeInsets.only(bottom: 30), + child: Text( + 'Un código será enviado a este numero de celular.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13.0, color: Color(0xFF65676B))), + ), + Padding( + padding: const EdgeInsets.only(bottom: 30), + child: Center( + child: PrimaryButtom( + onPressed: () { + controller.updatePhoneNumber( + completePhoneNumber.toString()); + }, + label: 'Enviar código'), + ), + ), + ], + ), + ) + : Center( + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 0, vertical: 20), + width: 400, + margin: const EdgeInsets.only(top: 30, left: 50, right: 50), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(bottom: 5), + child: Align( + alignment: Alignment.topLeft, + child: Text('Numero de celular', + style: TextStyle( + fontSize: 18.0, color: Color(0xFF65676B))), + )), + Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.only(bottom: 5), + child: IntlPhoneField( + controller: controller.newPhoneNo, + initialCountryCode: 'CO', + onChanged: (newPhoneNo) { + completePhoneNumber = newPhoneNo.completeNumber; + }, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: + BorderSide(color: Color(0xFFECECEC)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color.fromARGB(255, 184, 0, 0)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Color(0xFFECECEC)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Color(0xFFECECEC)), + borderRadius: BorderRadius.all( + Radius.circular(50), + )), + fillColor: Color.fromARGB(255, 239, 239, 239), + filled: true, + ), + ), + ), + ), + const Padding( + padding: EdgeInsets.only(bottom: 30), + child: Text( + 'Un código será enviado a este numero de celular.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13.0, color: Color(0xFF65676B))), + ), + Padding( + padding: const EdgeInsets.only(bottom: 30), + child: Center( + child: PrimaryButtom( + onPressed: () { + controller.updatePhoneNumber( + completePhoneNumber.toString()); + }, + label: 'Enviar código'), + ), + ), + ], + ), + ), + ), ), - )); + ); } } diff --git a/lib/src/screens/professional_profile.dart b/lib/src/screens/professional_profile.dart index 7081708..4c5342e 100644 --- a/lib/src/screens/professional_profile.dart +++ b/lib/src/screens/professional_profile.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; @@ -160,7 +161,7 @@ class ProfessionalProfileScreenState extends State { child: ListBody( children: [ GestureDetector( - child: Text( + child: const Text( textAlign: TextAlign.center, "Tomar foto", style: TextStyle(color: Color(0xFF2BA4EC)), @@ -173,9 +174,9 @@ class ProfessionalProfileScreenState extends State { Navigator.of(context).pop(); }, ), - Divider(color: Colors.black54), + const Divider(color: Colors.black54), GestureDetector( - child: Text( + child: const Text( textAlign: TextAlign.center, "Abrir Galería", style: TextStyle(color: Color(0xFF2BA4EC)), @@ -428,11 +429,11 @@ class ProfessionalProfileScreenState extends State { _showChoiceDialog(context); }, child: Container( - margin: EdgeInsets.symmetric(vertical: 50), + margin: const EdgeInsets.symmetric(vertical: 50), width: 100, height: 100, decoration: BoxDecoration( - color: Color(0xFF2BA4EC), + color: const Color(0xFF2BA4EC), borderRadius: BorderRadius.circular(50), ), child: const Icon( @@ -451,7 +452,7 @@ class ProfessionalProfileScreenState extends State { _showChoiceDialog(context); }, child: Container( - margin: EdgeInsets.symmetric(vertical: 50), + margin: const EdgeInsets.symmetric(vertical: 50), child: ClipOval( child: Image.memory( imageData, @@ -469,14 +470,14 @@ class ProfessionalProfileScreenState extends State { _showChoiceDialog(context); }, child: Container( - margin: EdgeInsets.symmetric(vertical: 50), + margin: const EdgeInsets.symmetric(vertical: 50), width: 100, height: 100, decoration: BoxDecoration( - color: Color(0xFF2BA4EC), + color: const Color(0xFF2BA4EC), borderRadius: BorderRadius.circular(50), ), - child: Icon( + child: const Icon( Icons.person, color: Colors.white, size: 90, @@ -492,14 +493,14 @@ class ProfessionalProfileScreenState extends State { _showChoiceDialog(context); }, child: Container( - margin: EdgeInsets.symmetric(vertical: 50), + margin: const EdgeInsets.symmetric(vertical: 50), width: 100, height: 100, decoration: BoxDecoration( - color: Color(0xFF2BA4EC), + color: const Color(0xFF2BA4EC), borderRadius: BorderRadius.circular(50), ), - child: Icon( + child: const Icon( Icons.person, color: Colors.white, size: 90, @@ -547,162 +548,162 @@ class ProfessionalProfileScreenState extends State { width: 300, padding: const EdgeInsets.only(top: 0), child: Form( - key: _formKey, - child: Column( - children: [ - TextFormField( - keyboardType: TextInputType.number, - controller: _cedulaController, - validator: (String? value) { - if (value == null || value.isEmpty) { - return 'Ingrese una cedula válida'; - } - return null; - }, - decoration: const InputDecoration( - prefixIcon: Icon(Icons.person_outline), - hintText: 'Cedula (Obligatorio)'), + key: _formKey, + child: Column( + children: [ + TextFormField( + keyboardType: TextInputType.number, + controller: _cedulaController, + validator: (String? value) { + if (value == null || value.isEmpty) { + return 'Ingrese una cedula válida'; + } + return null; + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.person_outline), + hintText: 'Cedula (Obligatorio)'), + ), + SizedBox(height: _space), + ElevatedButton( + onPressed: () { + _showChoiceDialogCedula(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD6F4FF), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(250, 50), ), - SizedBox(height: _space), - ElevatedButton( - onPressed: () { - _showChoiceDialogCedula(context); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFD6F4FF), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Cedula', + style: TextStyle( + color: Color(0xFF2BA4EC), + fontSize: 17, + ), ), - elevation: 0, - minimumSize: const Size(250, 50), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Cedula', - style: TextStyle( - color: Color(0xFF2BA4EC), - fontSize: 17, - ), - ), - const SizedBox(width: 15), - Icon( - image_cedula != null - ? Icons.check - : Icons.file_upload_outlined, - color: const Color(0xFF2BA4EC), - size: 30, - ), - ], - ), + const SizedBox(width: 15), + Icon( + image_cedula != null + ? Icons.check + : Icons.file_upload_outlined, + color: const Color(0xFF2BA4EC), + size: 30, + ), + ], ), - SizedBox(height: _space), - TextFormField( - readOnly: true, - onTap: () async { - final String? profesion = - (await Navigator.pushNamed( - context, '/profession')) as String?; + ), + SizedBox(height: _space), + TextFormField( + readOnly: true, + onTap: () async { + final String? profesion = (await Navigator.pushNamed( + context, '/profession')) as String?; - if (profesion != null) { - setState(() { - _profession = profesion; - }); - } - }, - decoration: InputDecoration( - prefixIcon: - const Icon(Icons.assignment_ind_rounded), - suffixIcon: const Icon(Icons.arrow_drop_down), - hintStyle: profession == '' - ? const TextStyle() - : const TextStyle(color: Colors.black87), - hintText: profession == '' - ? 'Profesión (Obligatorio)' - : profession), - ), - SizedBox(height: _space), - ElevatedButton( - onPressed: () { - _showChoiceDialogCertificado(context); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFD6F4FF), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - elevation: 0, - minimumSize: const Size(250, 50), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Certificado profesional', - style: TextStyle( - color: Color(0xFF2BA4EC), - fontSize: 17, - ), - ), - const SizedBox(width: 15), - Icon( - image_certificado != null - ? Icons.check - : Icons.file_upload_outlined, - color: const Color(0xFF2BA4EC), - size: 30, - ), - ], - ), - ), - SizedBox(height: _space), - TextFormField( - controller: _especializacionController, - decoration: const InputDecoration( - prefixIcon: Icon(Icons.assignment_ind_rounded), - hintText: 'Especialización'), - ), - SizedBox(height: _space), - ElevatedButton( - onPressed: () async { - final images = await getImage(3); + if (profesion != null) { setState(() { - images_especializacion = - images.map((e) => File(e!.path)).toList(); + _profession = profesion; }); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFD6F4FF), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - elevation: 0, - minimumSize: const Size(250, 50), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Especialización', - style: TextStyle( - color: Color(0xFF2BA4EC), - fontSize: 17, - ), - ), - const SizedBox(width: 15), - Icon( - images_especializacion.isEmpty - ? Icons.file_upload_outlined - : Icons.check, - color: const Color(0xFF2BA4EC), - size: 30, - ), - ], + } + }, + decoration: InputDecoration( + prefixIcon: + const Icon(Icons.assignment_ind_rounded), + suffixIcon: const Icon(Icons.arrow_drop_down), + hintStyle: profession == '' + ? const TextStyle() + : const TextStyle(color: Colors.black87), + hintText: profession == '' + ? 'Profesión (Obligatorio)' + : profession), + ), + SizedBox(height: _space), + ElevatedButton( + onPressed: () { + _showChoiceDialogCertificado(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD6F4FF), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), ), + elevation: 0, + minimumSize: const Size(250, 50), ), - ], - )), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Certificado profesional', + style: TextStyle( + color: Color(0xFF2BA4EC), + fontSize: 17, + ), + ), + const SizedBox(width: 15), + Icon( + image_certificado != null + ? Icons.check + : Icons.file_upload_outlined, + color: const Color(0xFF2BA4EC), + size: 30, + ), + ], + ), + ), + SizedBox(height: _space), + TextFormField( + controller: _especializacionController, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.assignment_ind_rounded), + hintText: 'Especialización'), + ), + SizedBox(height: _space), + ElevatedButton( + onPressed: () async { + final images = await getImage(3); + setState(() { + images_especializacion = + images.map((e) => File(e!.path)).toList(); + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD6F4FF), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(250, 50), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Especialización', + style: TextStyle( + color: Color(0xFF2BA4EC), + fontSize: 17, + ), + ), + const SizedBox(width: 15), + Icon( + images_especializacion.isEmpty + ? Icons.file_upload_outlined + : Icons.check, + color: const Color(0xFF2BA4EC), + size: 30, + ), + ], + ), + ), + ], + ), + ), ), Container( margin: const EdgeInsets.only( @@ -721,25 +722,27 @@ class ProfessionalProfileScreenState extends State { ), ], ), - child: Row( - children: const [ + child: const Row( + children: [ Icon( Icons.error_outline, size: 27, color: Colors.black54, ), SizedBox(width: 15), - Text( - 'Para añadir mas de una especialidad, \nenvie mas fotos y separe por comas (,).', - style: TextStyle(color: Colors.black, fontSize: 14), - ), + Expanded( + child: Text( + 'Para añadir mas de una especialidad, envie mas fotos y separe por comas (,).', + style: TextStyle(color: Colors.black, fontSize: 14), + ), + ) ], ), ), Container( alignment: Alignment.bottomCenter, margin: const EdgeInsets.only( - top: 80, right: 70, left: 70, bottom: 30), + top: 80, right: 20, left: 20, bottom: 30), child: ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { @@ -753,10 +756,11 @@ class ProfessionalProfileScreenState extends State { ), elevation: 0, minimumSize: const Size(250, 50), + maximumSize: const Size(350, 50), ), - child: Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Text( 'Enviar información', style: TextStyle( diff --git a/lib/src/screens/professional_profile_web.dart b/lib/src/screens/professional_profile_web.dart new file mode 100644 index 0000000..dafb555 --- /dev/null +++ b/lib/src/screens/professional_profile_web.dart @@ -0,0 +1,621 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:prosappco/src/authentication/authentication_repository.dart'; +import 'package:prosappco/src/components/photo_view_web.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:intl/intl.dart'; + +class ProfessionalProfileWebScreen extends StatefulWidget { + const ProfessionalProfileWebScreen({super.key}); + + @override + State createState() => + _ProfessionalProfileWebScreenState(); +} + +class _ProfessionalProfileWebScreenState + extends State { + final uid = AuthenticationRepository.instance.getCurrentUserUid(); + final FirebaseStorage storage = FirebaseStorage.instance; + late final FirebaseAuth _auth; + + // variables imagen + String selectedImage = ''; + String selectedCedulaImage = ''; + String selectedCertificadoImage = ''; + List selectedEspecializacionImages = []; + + XFile? file; + Uint8List? selectedImagInBytes; + XFile? image_cedula; + Uint8List? imageCedulaBytes; + XFile? image_certificado; + Uint8List? imageCertificadoBytes; + XFile? image_especializaciones; + Uint8List? imageespEcializacionesBytes; + + // controllers + final _formKey = GlobalKey(); + final TextEditingController _cedulaController = TextEditingController(); + final TextEditingController _especializacionController = + TextEditingController(); + + // variables + bool _isLoading = false; + String _profession = '...'; + String photoTemp = ''; + String photoCedulaTemp = ''; + String photoCertificadoTemp = ''; + String _photo = '...'; + + @override + void initState() { + super.initState(); + _auth = FirebaseAuth.instance; + if (_photo == '...') { + AuthenticationRepository.instance.getPhoto(uid.toString()).then( + (String s) => setState(() { + _photo = s; + }), + ); + } + + if (_profession == '...') { + AuthenticationRepository.instance.getProfession(uid.toString()).then( + (String s) => setState(() { + _profession = s; + }), + ); + } + } + + _selectFile(bool imageFrom) async { + FilePickerResult? fileResult = await FilePicker.platform.pickFiles(); + + if (fileResult != null) { + setState(() { + selectedImage = fileResult.files.first.name; + selectedImagInBytes = fileResult.files.first.bytes; + }); + } + } + + _selectFileCedula(bool imageFrom) async { + FilePickerResult? fileResult = await FilePicker.platform.pickFiles(); + + if (fileResult != null) { + setState(() { + selectedCedulaImage = fileResult.files.first.name; + imageCedulaBytes = fileResult.files.first.bytes; + }); + } + } + + _selectFileCertificado(bool imageFrom) async { + FilePickerResult? fileResult = await FilePicker.platform.pickFiles(); + + if (fileResult != null) { + setState(() { + selectedCertificadoImage = fileResult.files.first.name; + imageCertificadoBytes = fileResult.files.first.bytes; + }); + } + } + + _selectFilesEspecializaciones(bool imageFrom) async { + FilePickerResult? fileResult = + await FilePicker.platform.pickFiles(allowMultiple: true); + + if (fileResult != null) { + setState(() { + fileResult.files.forEach((element) { + selectedEspecializacionImages.add(element.name); + }); + + print('array - $selectedEspecializacionImages'); + }); + } + } + + Future sendInfo() async { + setState(() { + _isLoading = true; + }); + + final String cedula = _cedulaController.text.trim(); + + if (cedula.isEmpty) { + showSnackBar('Cedula invalida', 'Ingrese una cedula válida'); + return; + } + + if (imageCedulaBytes == null) { + showSnackBar('Cedula', 'Ingrese una imagen de su cedula'); + return; + } + + if (imageCertificadoBytes == null) { + showSnackBar('Certificado', 'Ingrese una imagen de su certificado'); + return; + } + + // Actualiza los datos del usuario en Firestore + await FirebaseFirestore.instance.collection('users').doc(uid).update({ + 'cedula': cedula, + 'estado': 'revision', + // 'especializaciones': especializaciones + }); + + // Sube las imágenes al storage de Firebase + await uploadCedula(); + await uploadCertificado(); + // uploadEspecializaciones(images_especializacion); + + // Actualiza la imagen de perfil si hay cambios + if (selectedImagInBytes != null) { + await uploadFile(); + await updateImage(photoTemp); + } + setState(() { + _isLoading = false; + }); + + // Navega a la siguiente pantalla + Navigator.pushReplacementNamed(context, '/solicitudEnviada'); + } + + uploadFile() async { + try { + final now = DateTime.now(); + final formattedDate = DateFormat('HHmmssddMMyyyy').format(now); + final milliseconds = (now.microsecondsSinceEpoch / 1000).round(); + final random = '$formattedDate$milliseconds'; + + final Reference ref = FirebaseStorage.instance + .ref() + .child('users') + .child(uid!) + .child('profile') + .child(random); + + final metaData = SettableMetadata(contentType: 'image/jpeg'); + + final UploadTask uploadTask = ref.putData(selectedImagInBytes!, metaData); + + final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true); + + photoTemp = ref.fullPath; + + if (snapshot.state == TaskState.success) { + return true; + } else { + return false; + } + } catch (e) { + print('web image error - $e'); + } + } + + uploadCedula() async { + try { + final now = DateTime.now(); + final formattedDate = DateFormat('HHmmssddMMyyyy').format(now); + final milliseconds = (now.microsecondsSinceEpoch / 1000).round(); + final random = 'c$formattedDate$milliseconds'; + + final Reference ref = FirebaseStorage.instance + .ref() + .child('users') + .child(uid!) + .child('cedula') + .child(random); + + final metaData = SettableMetadata(contentType: 'image/jpeg'); + + final UploadTask uploadTask = ref.putData(imageCedulaBytes!, metaData); + + final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true); + + photoCedulaTemp = ref.fullPath; + + if (snapshot.state == TaskState.success) { + updateImageCedula(photoCedulaTemp); + return true; + } else { + return false; + } + } catch (e) { + print('web image cedula error - $e'); + } + } + + uploadCertificado() async { + try { + final now = DateTime.now(); + final formattedDate = DateFormat('HHmmssddMMyyyy').format(now); + final milliseconds = (now.microsecondsSinceEpoch / 1000).round(); + final random = 'f$formattedDate$milliseconds'; + + final Reference ref = FirebaseStorage.instance + .ref() + .child('users') + .child(uid!) + .child('certificado_profesional') + .child(random); + + final metaData = SettableMetadata(contentType: 'image/jpeg'); + + final UploadTask uploadTask = + ref.putData(imageCertificadoBytes!, metaData); + + final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true); + + photoCertificadoTemp = ref.fullPath; + + if (snapshot.state == TaskState.success) { + updateImageCertificado(photoCertificadoTemp); + return true; + } else { + return false; + } + } catch (e) { + print('web image certificado error - $e'); + } + } + + Future updateImageCedula(image) async { + try { + final userRef = FirebaseFirestore.instance.collection('users').doc(uid); + final userSnapshot = await userRef.get(); + + if (userSnapshot.exists) { + await userRef.update({'imgCedula': image}); + print('¡Imagen de cédula actualizada correctamente!'); + } else { + await userRef.set({'imgCedula': image}); + print('¡Imagen de cédula agregada correctamente!'); + } + } catch (e) { + print('Error al agregar o actualizar la imagen de cédula: $e'); + } + } + + Future updateImageCertificado(image) async { + try { + final userRef = FirebaseFirestore.instance.collection('users').doc(uid); + final userSnapshot = await userRef.get(); + + if (userSnapshot.exists) { + await userRef.update({'imgCertificado': image}); + print('¡Imagen de certificado actualizada correctamente!'); + } else { + await userRef.set({'imgCertificado': image}); + print('¡Imagen de certificado agregada correctamente!'); + } + } catch (e) { + print('Error al agregar o actualizar la imagen de certificado: $e'); + } + } + + Future updateImage(image) async { + try { + await FirebaseFirestore.instance + .collection('users') + .doc(uid) + .update({'photo': image}); + + print('imagen actualizada correctamente'); + } 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'); + } + } + + void showSnackBar(String title, String message) { + Get.snackbar( + title, + message, + snackPosition: SnackPosition.TOP, + ); + } + + @override + Widget build(BuildContext context) { + String profession = _profession.toString(); + double _space = 10; + + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Perfil profesional'), + body: SingleChildScrollView( + child: Center( + child: _isLoading + ? const CircularProgressIndicator() + : Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20), + child: (selectedImagInBytes != null) + ? LocalPhotoWeb(file: selectedImagInBytes) + : ReferencePhotoWeb(ref: storage.ref().child(_photo)), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: ElevatedButton.icon( + onPressed: () { + _selectFile(true); + }, + icon: const Icon(Icons.image), + label: const Text('Elige una imagen'), + ), + ), + Container( + width: 300, + child: Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + keyboardType: TextInputType.number, + controller: _cedulaController, + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly // Solo permite caracteres numéricos + ], + validator: (String? value) { + if (value == null || value.isEmpty) { + return 'Ingrese una cedula válida'; + } + return null; + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.person_outline), + hintText: 'Cedula (Obligatorio)', + ), + ), + SizedBox(height: _space), + ElevatedButton( + onPressed: () { + _selectFileCedula(true); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD6F4FF), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(250, 50), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Cedula', + style: TextStyle( + color: Color(0xFF2BA4EC), + fontSize: 17, + ), + ), + const SizedBox(width: 15), + Icon( + imageCedulaBytes != null + ? Icons.check + : Icons.file_upload_outlined, + color: const Color(0xFF2BA4EC), + size: 30, + ), + ], + ), + ), + SizedBox(height: _space), + TextFormField( + readOnly: true, + onTap: () async { + final String? profesion = + (await Navigator.pushNamed( + context, '/profession')) as String?; + + if (profesion != null) { + setState(() { + _profession = profesion; + }); + } + }, + decoration: InputDecoration( + prefixIcon: + const Icon(Icons.assignment_ind_rounded), + suffixIcon: const Icon(Icons.arrow_drop_down), + hintStyle: profession == '' + ? const TextStyle() + : const TextStyle(color: Colors.black87), + hintText: profession == '' + ? 'Profesión (Obligatorio)' + : profession), + ), + SizedBox(height: _space), + ElevatedButton( + onPressed: () { + _selectFileCertificado(true); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD6F4FF), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(250, 50), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Certificado profesional', + style: TextStyle( + color: Color(0xFF2BA4EC), + fontSize: 17, + ), + ), + const SizedBox(width: 15), + Icon( + imageCertificadoBytes != null + ? Icons.check + : Icons.file_upload_outlined, + color: const Color(0xFF2BA4EC), + size: 30, + ), + ], + ), + ), + SizedBox(height: _space), + TextFormField( + controller: _especializacionController, + decoration: const InputDecoration( + prefixIcon: + Icon(Icons.assignment_ind_rounded), + hintText: 'Especialización'), + ), + SizedBox(height: _space), + ElevatedButton( + onPressed: () async { + _selectFilesEspecializaciones(true); + + // final images = await getImage(3); + // setState(() { + // images_especializacion = + // images.map((e) => File(e!.path)).toList(); + // }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD6F4FF), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(250, 50), + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Especialización', + style: TextStyle( + color: Color(0xFF2BA4EC), + fontSize: 17, + ), + ), + SizedBox(width: 15), + // Icon( + // images_especializacion.isEmpty + // ? Icons.file_upload_outlined + // : Icons.check, + // color: const Color(0xFF2BA4EC), + // size: 30, + // ), + ], + ), + ), + ], + ), + ), + ), + Container( + width: 450, + margin: const EdgeInsets.only( + left: 40, right: 40, top: 40, bottom: 0), + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 15), + 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: 27, + color: Colors.black54, + ), + SizedBox(width: 15), + Expanded( + child: Text( + 'Para añadir mas de una especialidad, envie mas fotos y separe por comas (,).', + style: + TextStyle(color: Colors.black, fontSize: 14), + ), + ), + ], + ), + ), + Container( + alignment: Alignment.bottomCenter, + margin: const EdgeInsets.only( + top: 80, right: 20, left: 20, bottom: 30), + child: ElevatedButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + sendInfo(); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(250, 50), + maximumSize: const Size(350, 50), + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Enviar información', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 17, + ), + ), + SizedBox(width: 15), + Icon( + Icons.send, + color: Colors.white, + size: 20, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/profile.dart b/lib/src/screens/profile.dart index bd16274..039dee7 100644 --- a/lib/src/screens/profile.dart +++ b/lib/src/screens/profile.dart @@ -5,13 +5,13 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'dart:io'; +import 'package:intl/intl.dart'; import 'package:prosappco/src/authentication/authentication_repository.dart'; import 'package:prosappco/src/components/pop_appbar.dart'; import 'package:prosappco/src/controllers/add_name_email_city.dart'; import 'package:prosappco/src/screens/city.dart'; import 'package:prosappco/src/screens/new_number.dart'; import 'package:prosappco/src/screens/new_password.dart'; -import 'package:prosappco/src/screens/service.dart'; import 'package:prosappco/src/services/select_image_profile.dart'; import '../components/photo_view.dart'; @@ -103,14 +103,13 @@ class _ProfileScreenState extends State { } Future uploadImage(File image) async { - final String namefile = image.path.split('/').last; + 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(namefile); + Reference ref = + storage.ref().child('users').child(uid!).child('profile').child(random); final UploadTask uploadTask = ref.putFile(image); @@ -231,7 +230,7 @@ class _ProfileScreenState extends State { } if (newEmail.isEmpty) { - // Si el nuevo nombre está vacío, no lo actualizamos y mostramos un mensaje al usuario + // Si el nuevo email está vacío, no lo actualizamos y mostramos un mensaje al usuario Get.snackbar( 'Correo Invalido', 'Ingrese un email válido.', @@ -374,12 +373,8 @@ class _ProfileScreenState extends State { child: Container( margin: const EdgeInsets.symmetric(vertical: 50), child: (imagen_to_upload != null) - ? LocalPhoto( - file: imagen_to_upload!, - ) - : ReferencePhoto( - ref: storage.ref().child(_photo), - )), + ? LocalPhoto(file: imagen_to_upload!) + : ReferencePhoto(ref: storage.ref().child(_photo))), ), Container( width: 300, diff --git a/lib/src/screens/profile_web.dart b/lib/src/screens/profile_web.dart new file mode 100644 index 0000000..ba0731d --- /dev/null +++ b/lib/src/screens/profile_web.dart @@ -0,0 +1,460 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:prosappco/src/authentication/authentication_repository.dart'; +import 'package:prosappco/src/components/photo_view_web.dart'; +import 'package:prosappco/src/components/pop_appbar.dart'; +import 'package:prosappco/src/screens/city.dart'; +import 'package:prosappco/src/screens/new_number.dart'; +import 'package:prosappco/src/screens/new_password.dart'; + +class ProfileWebScreen extends StatefulWidget { + const ProfileWebScreen({super.key}); + + @override + State createState() => _ProfileWebScreenState(); +} + +class _ProfileWebScreenState extends State { + final uid = AuthenticationRepository.instance.getCurrentUserUid(); + final FirebaseStorage storage = FirebaseStorage.instance; + late final FirebaseAuth _auth; + + // variables imagen + String selectedImage = ''; + XFile? file; + Uint8List? selectedImagInBytes; + String photoTemp = ''; + + // controladores + final _formKey = GlobalKey(); + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _phoneNumberController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + bool _obscureText = true; + + // variables + String _ciudad = '...'; + String _photo = '...'; + String? _email = ''; + + @override + void initState() { + super.initState(); + + final uid = AuthenticationRepository.instance.getCurrentUserUid(); + _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!; + } + + _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; + })); + } + } + + _selectFile(bool imageFrom) async { + FilePickerResult? fileResult = await FilePicker.platform.pickFiles(); + + if (fileResult != null) { + setState(() { + selectedImage = fileResult.files.first.name; + selectedImagInBytes = fileResult.files.first.bytes; + }); + } + } + + _uploadFile() async { + try { + final now = DateTime.now(); + final formattedDate = DateFormat('HHmmssddMMyyyy').format(now); + final milliseconds = (now.microsecondsSinceEpoch / 1000).round(); + final random = '$formattedDate$milliseconds'; + + final Reference ref = FirebaseStorage.instance + .ref() + .child('users') + .child(uid!) + .child('profile') + .child(random); + + final metaData = SettableMetadata(contentType: 'image/jpeg'); + + final UploadTask uploadTask = ref.putData(selectedImagInBytes!, metaData); + + final TaskSnapshot snapshot = await uploadTask.whenComplete(() => true); + + photoTemp = ref.fullPath; + + if (snapshot.state == TaskState.success) { + return true; + } else { + return false; + } + } catch (e) { + print('web image error - $e'); + } + } + + Future updateInfo() async { + final currentUser = _auth.currentUser; + final currentPhoneNumber = _auth.currentUser!.phoneNumber; + + String newName = _nameController.text.trim(); + String newEmail = _emailController.text.trim(); + String newPassword = _passwordController.text.trim(); + + if (newName.isEmpty) { + // Si el nuevo nombre está vacío, no lo actualizamos y mostramos un mensaje al usuario + Get.snackbar( + 'Nombre Invalido', + 'Ingrese un nombre válido.', + snackPosition: SnackPosition.BOTTOM, + ); + return; + } + if (newEmail.isEmpty) { + // Si el nuevo nombre está vacío, no lo actualizamos y mostramos un mensaje al usuario + Get.snackbar( + 'Correo Invalido', + 'Ingrese 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(), + }); + print('Nombre actualizado correctamente'); + } catch (e) { + print('Error al actualizar el nombre: $e'); + } + } + + if (currentUser?.email != newEmail) { + if (newPassword.isNotEmpty) { + await FirebaseFirestore.instance.collection('users').doc(uid).update({ + 'email': newEmail, + }); + updateEmailAndPassword(newEmail, newPassword); + } else { + Get.snackbar( + 'Contraseña Invalida', + 'Porfavor ingrese una contraseña.', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + try { + await FirebaseFirestore.instance + .collection('users') + .doc(uid) + .update({'phoneNumber': currentPhoneNumber}); + } catch (e) { + print('e'); + } + + try { + if (selectedImagInBytes == null) { + return; + } else { + await _uploadFile(); + updateImage(photoTemp); + + //image + } + } catch (e) { + print('Error al actualizar la imagen de perfil $e'); + } + } + + Future updateImage(image) async { + try { + await FirebaseFirestore.instance + .collection('users') + .doc(uid) + .update({'photo': image}); + + print('imagen actualizada correctamente'); + } 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 updateEmailAndPassword(String email, String password) async { + final User? user = FirebaseAuth.instance.currentUser; + if (user != null) { + try { + await user.updateEmail(email); + await user.updatePassword(password); + } catch (e) { + Get.snackbar( + 'Agregar correo', + 'Inicie sesión para asegurarnos de que seas tú .', + snackPosition: SnackPosition.BOTTOM, + ); + AuthenticationRepository.instance.logout(); + } + } + } + + @override + Widget build(BuildContext context) { + String city = _ciudad.toString(); + + return Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Perfil'), + body: SingleChildScrollView( + reverse: true, + child: Center( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20), + child: (selectedImagInBytes != null) + ? LocalPhotoWeb(file: selectedImagInBytes) + : ReferencePhotoWeb(ref: storage.ref().child(_photo)), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: ElevatedButton.icon( + onPressed: () { + _selectFile(true); + }, + icon: const Icon(Icons.image), + label: const Text('Elige una imagen'), + ), + ), + Container( + width: 300, + child: Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: _nameController, + maxLength: 50, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Porfavor ingrese un nombre.'; + } + if (value.length < 5) { + return 'Debe tener al menos 5 caracteres.'; + } + return null; + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.person_outline), + hintText: 'Nombre (Obligatorio)', + ), + ), + _email != null + ? TextFormField( + onTap: () { + Navigator.push(context, CupertinoPageRoute( + builder: (BuildContext context) { + return NewPasswordScreen(); + }, + )); + }, + 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( + 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, + ), + ), + const SizedBox(height: 20.0), + TextFormField( + controller: _phoneNumberController, + readOnly: true, + onTap: () { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (BuildContext context) { + return const NewNumberScreen(); + }, + ), + ); + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.phone_android), + suffixIcon: Icon(Icons.edit_outlined), + hintText: '+57'), + ), + Container( + alignment: Alignment.bottomCenter, + margin: const EdgeInsets.only(top: 50), + padding: const EdgeInsets.only(bottom: 30), + child: ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + await updateInfo().whenComplete(() { + Get.snackbar( + 'Perfil', + 'Información actualizada correctamente.', + snackPosition: SnackPosition.TOP, + ); + }); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF2BA4EC), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + elevation: 0, + minimumSize: const Size(200, 50), + ), + child: const Text( + 'Guardar', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 17, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/service.dart b/lib/src/screens/service.dart index ccbdda6..95ee7f2 100644 --- a/lib/src/screens/service.dart +++ b/lib/src/screens/service.dart @@ -330,8 +330,8 @@ class _ServiceScreenState extends State { coordinates = position.target; }); }, - gestureRecognizers: < - Factory>{ + gestureRecognizers: >{ Factory( () => EagerGestureRecognizer(), ), diff --git a/pubspec.lock b/pubspec.lock index 7a4005f..6cfe89a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "867b77e2367bc502dcd4d5a66302615409f04eb20ed82ba1c0ba073f9107e018" + sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.2" args: dependency: transitive description: @@ -157,50 +157,50 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "94c229e296a5b9ee5c8cda918e0b320e3a0cc4f6a349cd410c427da347f2a244" + sha256: "19508428ca37f611ae47067ee6ebb3ba5a27014941177cc224f71bf7d0720cdd" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.6.2" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "1217d8aa313b49d58b489aa8879544563abc8793d9612ff20d8df193f202aedc" + sha256: e46e136a6f6eec88b30f12445ff7f5b19b23b7ede694921ced4f8eba8eb634f6 url: "https://pub.dev" source: hosted - version: "6.12.0" + version: "6.15.2" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: bf7f1a87995a58b0f07dc617806dabd7ff25c64be7fa47b41ab1bb9a485b0062 + sha256: "553bd576d793d05b920971a2c7ab02bd049d4971153702074ea2555877efd392" url: "https://pub.dev" source: hosted - version: "5.2.10" + version: "5.5.2" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: dcf54c170c5371ad0e79229d0fb372c58262ae0968b7de222bf28e51dd236be0 + sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: ae79f335f6c7f2dadb00c98c429da2ca905d265e0225fb5e7dfa62ac3accad48 + sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: e57ef862257a0d977c1308d02e2fbb9b68525e6d85711b08f3df8cec836fb444 + sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.0" firebase_messaging: dependency: "direct main" description: @@ -229,26 +229,26 @@ packages: dependency: "direct main" description: name: firebase_storage - sha256: a27909491c25acef90acc932be4d87bafb9d33fb98678edd2f81eaec9568a109 + sha256: ba695c4905da360a9daa5ecb12a8c272b2dfd681a12aaf109fa7666abc54f8a5 url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "11.2.2" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface - sha256: f254e064890df4ee588f10bea06d679e047018910451a4bc3c529b9791adb0a9 + sha256: "81e8498e26c4ba0a88400666e4f6777ebecf0c49e89d54f9f0f989b249c29abb" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.4.2" firebase_storage_web: dependency: transitive description: name: firebase_storage_web - sha256: b6d3c104fecafdce5fe1795306d33856c9a11fa76f73fe7eb062430fc151d686 + sha256: "4406f263c7a6a53502bde436e0c53ced8385142a2f8ef4005f0781749d87d286" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.6.2" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index a46d217..74da305 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,8 +37,9 @@ dependencies: cupertino_icons: ^1.0.2 # Google Sign In - firebase_auth: - firebase_core: + firebase_auth: ^4.6.2 + firebase_core: ^2.13.1 + file_picker: ^5.3.2 google_sign_in: provider: get: @@ -59,11 +60,10 @@ dependencies: community_material_icon: ^5.9.55 flutter_email_sender: ^5.2.0 webview_flutter: ^4.2.0 - firebase_storage: + firebase_storage: ^11.2.2 firebase_messaging: ^14.5.0 flutter_local_notifications: ^14.0.0+1 responsive_builder: ^0.7.0 - file_picker: ^5.3.2 http: ^0.13.5 dev_dependencies: