diff --git a/lib/blocs/auth_bloc/auth_bloc.dart b/lib/blocs/auth_bloc/auth_bloc.dart index 9797e58..1f7df10 100644 --- a/lib/blocs/auth_bloc/auth_bloc.dart +++ b/lib/blocs/auth_bloc/auth_bloc.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:user_repository/user_repository.dart'; diff --git a/lib/blocs/profile_bloc/profile_bloc.dart b/lib/blocs/profile_bloc/profile_bloc.dart index b9262c6..469fb8d 100644 --- a/lib/blocs/profile_bloc/profile_bloc.dart +++ b/lib/blocs/profile_bloc/profile_bloc.dart @@ -11,27 +11,33 @@ class ProfileBloc extends Bloc { ProfileBloc({required UserRepository userRepository}) : _userRepository = userRepository, super(UpdateUserInfoInitial()) { - on(_onUploadPicture); + // on(_onUploadPicture); on(_onUpdateUserInfo); } - void _onUploadPicture(UploadPicture event, Emitter emit) async { - emit(UploadPictureLoading()); - try { - String userImage = - await _userRepository.uploadPicture(event.file, event.userId); - emit(UploadPictureSuccess(userImage)); - } catch (e) { - emit(UploadPictureFailure()); - } - } + // void _onUploadPicture(UploadPicture event, Emitter emit) async { + // emit(UploadPictureLoading()); + // try { + // String userImage = + // await _userRepository.uploadPicture(event.file, event.userId); + // emit(UploadPictureSuccess(userImage)); + // } catch (e) { + // emit(UploadPictureFailure()); + // } + // } void _onUpdateUserInfo( UpdateUserInfo event, Emitter emit) async { emit(UpdateUserInfoLoading()); try { - await _userRepository.updateUserInfo(event.userId, { + await _userRepository.updateUserInfo(event.userId, event.picture, { 'name': event.name, + // 'picture': event.picture, + 'nickname': event.nickname, + 'email': event.email, + 'phone': event.phone, + 'birthDate': event.birthDate, + 'gender': event.gender }); emit(const UpdateUserInfoSuccess()); } catch (e) { diff --git a/lib/blocs/profile_bloc/profile_event.dart b/lib/blocs/profile_bloc/profile_event.dart index 0579ff5..d753a46 100644 --- a/lib/blocs/profile_bloc/profile_event.dart +++ b/lib/blocs/profile_bloc/profile_event.dart @@ -4,7 +4,7 @@ abstract class ProfileEvent extends Equatable { const ProfileEvent(); @override - List get props => []; + List get props => []; } class UploadPicture extends ProfileEvent { @@ -14,15 +14,31 @@ class UploadPicture extends ProfileEvent { const UploadPicture(this.file, this.userId); @override - List get props => [file, userId]; + List get props => [file, userId]; } class UpdateUserInfo extends ProfileEvent { final String userId; - final String name; + final String picture; + final String? name; + final String? nickname; + final String? email; + final String? phone; + final String? birthDate; + final String? gender; - const UpdateUserInfo(this.userId, this.name); + const UpdateUserInfo({ + required this.userId, + required this.picture, + this.name, + this.nickname, + this.email, + this.phone, + this.birthDate, + this.gender, + }); @override - List get props => [userId, name]; + List get props => + [userId, name, nickname, email, phone, picture, birthDate, gender]; } diff --git a/lib/src/presentation/widgets/profile/birth_date_picker.dart b/lib/components/birth_date_picker.dart similarity index 58% rename from lib/src/presentation/widgets/profile/birth_date_picker.dart rename to lib/components/birth_date_picker.dart index 4954233..06c57bd 100644 --- a/lib/src/presentation/widgets/profile/birth_date_picker.dart +++ b/lib/components/birth_date_picker.dart @@ -5,11 +5,10 @@ class BirthDatePicker extends StatefulWidget { final TextEditingController controller; const BirthDatePicker( - {Key? key, required this.onDateSelected, required this.controller}) - : super(key: key); + {super.key, required this.onDateSelected, required this.controller}); @override - _BirthDatePickerState createState() => _BirthDatePickerState(); + State createState() => _BirthDatePickerState(); } class _BirthDatePickerState extends State { @@ -39,10 +38,20 @@ class _BirthDatePickerState extends State { _selectDate(context); }, readOnly: true, - decoration: const InputDecoration( - prefixIcon: Icon(Icons.calendar_month), - suffixIcon: Icon(Icons.arrow_drop_down), - hintText: 'Fecha de nacimiento', + decoration: InputDecoration( + labelText: 'Fecha de nacimiento', + prefixIcon: const Icon(Icons.calendar_month_rounded), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + errorBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.red), + borderRadius: BorderRadius.circular(10.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.red, width: 2.0), + borderRadius: BorderRadius.circular(10.0), + ), ), controller: widget.controller, ); diff --git a/lib/src/presentation/widgets/profile/gender_dropdown.dart b/lib/components/gender_dropdown.dart similarity index 62% rename from lib/src/presentation/widgets/profile/gender_dropdown.dart rename to lib/components/gender_dropdown.dart index 7a212c5..4a3adc3 100644 --- a/lib/src/presentation/widgets/profile/gender_dropdown.dart +++ b/lib/components/gender_dropdown.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; class GenderDropdown extends StatefulWidget { final Function(String) onChanged; - const GenderDropdown({Key? key, required this.onChanged}) : super(key: key); + const GenderDropdown({super.key, required this.onChanged}); @override - _GenderDropdownState createState() => _GenderDropdownState(); + State createState() => _GenderDropdownState(); } class _GenderDropdownState extends State { @@ -15,16 +15,24 @@ class _GenderDropdownState extends State { @override Widget build(BuildContext context) { return InputDecorator( - decoration: const InputDecoration( - border: UnderlineInputBorder(), - prefixIcon: Icon(Icons.people_outline), - contentPadding: EdgeInsets.symmetric(horizontal: 8.0), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + prefixIcon: const Icon(Icons.groups), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 7.0, + ), ), child: DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, value: selectedGender, - hint: const Text('Selecciona tu género'), + hint: const Text( + 'Selecciona tu género', + style: TextStyle(fontSize: 16.0), + ), onChanged: (String? newValue) { setState(() { selectedGender = newValue; diff --git a/lib/components/general_drawer_header.dart b/lib/components/general_drawer_header.dart index d9fbf07..ca78cf6 100644 --- a/lib/components/general_drawer_header.dart +++ b/lib/components/general_drawer_header.dart @@ -26,10 +26,13 @@ class GeneralDrawerHeader extends StatelessWidget { style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( - context.read().state.user!.email ?? '', + context.read().state.user!.email ?? + context.read().state.user!.phone ?? + '', style: const TextStyle(fontSize: 12), ), - leading: context.read().state.user!.picture == "" + leading: context.read().state.user!.picture == "" || + context.read().state.user!.picture == null ? Container( width: 80, height: 80, @@ -51,7 +54,7 @@ class GeneralDrawerHeader extends StatelessWidget { shape: BoxShape.circle, image: DecorationImage( image: NetworkImage( - context.read().state.user!.picture!, + context.read().state.user?.picture ?? '', ), fit: BoxFit.cover, ), diff --git a/lib/screens/about/about_screen.dart b/lib/screens/about/about_screen.dart new file mode 100644 index 0000000..162ac97 --- /dev/null +++ b/lib/screens/about/about_screen.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class AboutScreen extends StatelessWidget { + const AboutScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Placeholder(); + } +} diff --git a/lib/screens/profile/profile_screen.dart b/lib/screens/profile/profile_screen.dart index d973285..a3edc67 100644 --- a/lib/screens/profile/profile_screen.dart +++ b/lib/screens/profile/profile_screen.dart @@ -1,9 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:image_picker/image_picker.dart'; import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; import 'package:prosappco/blocs/profile_bloc/profile_bloc.dart'; +import 'package:prosappco/components/birth_date_picker.dart'; +import 'package:prosappco/components/gender_dropdown.dart'; +import 'package:prosappco/components/general_primary_button.dart'; import 'package:prosappco/src/components/pop_appbar.dart'; class ProfileScreen extends StatefulWidget { @@ -15,10 +21,19 @@ class ProfileScreen extends StatefulWidget { class _ProfileScreenState extends State { final TextEditingController _nameController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _phoneController = TextEditingController(); + final TextEditingController _birthDateController = TextEditingController(); + final TextEditingController _genderController = TextEditingController(); + XFile? _imageFile; @override void dispose() { _nameController.dispose(); + _emailController.dispose(); + _phoneController.dispose(); + _birthDateController.dispose(); + _genderController.dispose(); super.dispose(); } @@ -40,118 +55,183 @@ class _ProfileScreenState extends State { body: BlocBuilder(builder: (context, state) { if (state.status == MyUserStatus.success) { _nameController.text = state.user!.name ?? ''; + _emailController.text = state.user!.email ?? ''; + _phoneController.text = state.user!.phone ?? ''; - return Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - GestureDetector( - onTap: () async { - final ImagePicker picker = ImagePicker(); - final XFile? image = await picker.pickImage( - source: ImageSource.gallery, - maxHeight: 500, - maxWidth: 500, - imageQuality: 40, - ); + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GestureDetector( + onTap: () async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: ImageSource.gallery, + maxHeight: 500, + maxWidth: 500, + imageQuality: 40, + ); - if (image != null) { - context.read().add( - UploadPicture(image.path, state.user!.id), - ); - } - }, - child: state.user!.picture == "" - ? Container( - width: 120, - height: 120, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, - ), - child: Icon( - CupertinoIcons.person, - color: Colors.grey.shade400, - size: 40, - ), - ) - : Container( - width: 150, - height: 150, - decoration: BoxDecoration( - color: Colors.grey, - shape: BoxShape.circle, - image: DecorationImage( - image: NetworkImage( - context - .read() - .state - .user! - .picture!, + if (image != null) { + // context + // .read() + // .add(UploadPicture(image.path, state.user!.id)); + setState(() { + _imageFile = image; + }); + } + }, + child: state.user!.picture == "" || + state.user!.picture == null + ? _imageFile != null + ? Container( + width: 150, + height: 150, + decoration: BoxDecoration( + color: Colors.grey, + shape: BoxShape.circle, + image: DecorationImage( + image: FileImage(File(_imageFile!.path)), + fit: BoxFit.contain, + ), + ), + ) + : Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), + child: Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 40, + ), + ) + : Container( + width: 150, + height: 150, + decoration: BoxDecoration( + color: Colors.grey, + shape: BoxShape.circle, + image: DecorationImage( + image: NetworkImage(context + .read() + .state + .user + ?.picture ?? + ''), + fit: BoxFit.contain, ), - fit: BoxFit.contain, ), ), - ), - ), - const SizedBox(height: 20.0), - TextFormField( - controller: _nameController, - decoration: const InputDecoration( - labelText: 'Nombre', - prefixIcon: Icon(Icons.person), - hintText: 'Nombre (obligatorio)', - border: OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(25.0), - )), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red), + ), + const SizedBox(height: 20.0), + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + labelText: 'Nombre', + prefixIcon: Icon(Icons.person), + hintText: 'Nombre (obligatorio)', + border: OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10.0), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2.0), + ), ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red, width: 2.0), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Por favor, ingrese su nombre'; + } + return null; + }, + ), + const SizedBox(height: 20.0), + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + labelText: 'Email', + prefixIcon: Icon(Icons.email_rounded), + hintText: 'Email', + border: OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10.0), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2.0), + ), ), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Por favor, ingrese su nombre'; - } - return null; - }, - ), - const SizedBox(height: 20.0), - TextFormField( - decoration: const InputDecoration( - labelText: 'Nombre', - prefixIcon: Icon(Icons.person), - hintText: 'Nombre (obligatorio)', - border: OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(25.0), - )), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.red, width: 2.0), + const SizedBox(height: 20.0), + TextFormField( + controller: _phoneController, + decoration: const InputDecoration( + labelText: 'Número de Teléfono', + prefixIcon: Icon(Icons.phone_android_rounded), + hintText: '+57', + border: OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10.0), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2.0), + ), ), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Por favor, ingrese su nombre'; - } - return null; - }, - ), - ElevatedButton( - onPressed: () { - context.read().add( - UpdateUserInfo(state.user!.id, _nameController.text)); - }, - child: const Text('Guardar'), - ), - ], + const SizedBox(height: 20.0), + BirthDatePicker( + onDateSelected: (birthDay) { + _birthDateController.text = + DateFormat('dd/MM/yyyy').format(birthDay); + }, + controller: _birthDateController, + ), + const SizedBox(height: 20.0), + GenderDropdown(onChanged: (selectedGender) { + _genderController.text = selectedGender; + }), + ElevatedButton( + onPressed: () { + context.read().add( + UpdateUserInfo( + userId: state.user!.id, + picture: _imageFile?.path ?? + state.user!.picture ?? + '', + name: _nameController.text.isEmpty + ? null + : _nameController.text, + nickname: + _nameController.text.trim().toLowerCase(), + email: _emailController.text.isEmpty + ? null + : _emailController.text, + phone: _phoneController.text.isEmpty + ? null + : _phoneController.text, + birthDate: _birthDateController.text, + gender: _genderController.text, + ), + ); + }, + child: const Text('Guardar'), + ), + ], + ), ), ); } else { diff --git a/packages/user_repository/lib/src/repositories/firebase_user_repository.dart b/packages/user_repository/lib/src/repositories/firebase_user_repository.dart index 068a261..40678b3 100644 --- a/packages/user_repository/lib/src/repositories/firebase_user_repository.dart +++ b/packages/user_repository/lib/src/repositories/firebase_user_repository.dart @@ -19,7 +19,14 @@ class FirebaseUserRepository implements UserRepository { FirebaseUserRepository(this._firebaseAuth) { _firebaseAuth.userChanges().listen((user) async { if (user != null) { - await updateFromFirebase(user.uid); + await updateFromFirebase2( + userId: user.uid, + email: user.email, + name: user.displayName, + phone: user.phoneNumber, + picture: user.photoURL, + nickname: user.displayName?.trim().toLowerCase(), + ); } else { _userStreamController.add(null); } @@ -27,17 +34,35 @@ class FirebaseUserRepository implements UserRepository { } Future updateFromFirebase(String userId) async { + return updateFromFirebase2(userId: userId); + } + + Future updateFromFirebase2({ + required String userId, + String? email, + String? name, + String? phone, + String? picture, + String? nickname, + }) async { try { - final myUser = await getMyUser(userId); + var myUser = await getMyUser(userId); + + if (myUser == null) { + await createUser(MyUser( + id: userId, + email: email, + name: name, + phone: phone, + picture: picture, + nickname: name?.trim().toLowerCase(), + )); + myUser = await getMyUser(userId); + } _userStreamController.add(myUser); } catch (e) { - try { - await createUser(userId); - final myUser = await getMyUser(userId); - _userStreamController.add(myUser); - } catch (e) { - _userStreamController.add(null); - } + log('xd -- Error updating from firebase ${e.toString()}'); + _userStreamController.add(null); } } @@ -55,12 +80,13 @@ class FirebaseUserRepository implements UserRepository { @override Future signUp(MyUser myUser, String password) async { try { - UserCredential user = await _firebaseAuth.createUserWithEmailAndPassword( + UserCredential userCredential = + await _firebaseAuth.createUserWithEmailAndPassword( email: myUser.email!, password: password, ); - myUser = myUser.copyWith(id: user.user!.uid); + myUser = myUser.copyWith(id: userCredential.user!.uid); return myUser; } catch (e) { @@ -102,6 +128,14 @@ class FirebaseUserRepository implements UserRepository { var credentials = await _firebaseAuth.signInWithCredential( PhoneAuthProvider.credential( verificationId: verificationId, smsCode: code)); + + if (credentials.user == null) { + await setUserData(MyUser( + id: credentials.user?.uid ?? '', + phone: credentials.user?.phoneNumber ?? '', + )); + } + return credentials.user != null ? true : false; } catch (e) { if (e is FirebaseAuthException) { @@ -165,11 +199,16 @@ class FirebaseUserRepository implements UserRepository { // Get user data @override - Future getMyUser(String myUserId) async { + Future getMyUser(String myUserId) async { try { return usersCollection.doc(myUserId).get().then((value) { + final valueData = value.data(); + if (valueData == null || valueData.isEmpty || !value.exists) { + return null; + } + return MyUser.fromEntity( - MyUserEntity.fromDocument(value.data()!), + MyUserEntity.fromDocument(valueData), ); }); } catch (e) { @@ -179,8 +218,22 @@ class FirebaseUserRepository implements UserRepository { } @override - Future updateUserInfo(String userId, Map data) async { + Future updateUserInfo( + String userId, String? picture, Map data) async { try { + if (picture != null) { + File imageFile = File(picture); + Reference firebaseStoreRef = + FirebaseStorage.instance.ref().child('$userId/PP/${userId}_lead'); + + await firebaseStoreRef.putFile( + imageFile, + ); + + String url = await firebaseStoreRef.getDownloadURL(); + await usersCollection.doc(userId).update({'picture': url}); + } + await usersCollection.doc(userId).update(data); await updateFromFirebase(userId); @@ -209,16 +262,9 @@ class FirebaseUserRepository implements UserRepository { } @override - Future createUser(String userId) async { + Future createUser(MyUser myUser) async { try { - await usersCollection.doc(userId).set({ - 'id': userId, - 'name': '', - 'nickname': '', - 'email': '', - 'phone': '', - 'picture': '', - }); + await usersCollection.doc(myUser.id).set(myUser.toEntity().toDocument()); } catch (e) { log(e.toString()); } diff --git a/packages/user_repository/lib/src/repositories/user_repo.dart b/packages/user_repository/lib/src/repositories/user_repo.dart index fba7e52..04053b4 100644 --- a/packages/user_repository/lib/src/repositories/user_repo.dart +++ b/packages/user_repository/lib/src/repositories/user_repo.dart @@ -20,11 +20,12 @@ abstract class UserRepository { Future setUserData(MyUser user); - Future getMyUser(String myUserId); + Future getMyUser(String myUserId); - Future updateUserInfo(String userId, Map data); + Future updateUserInfo( + String userId, String picture, Map data); Future uploadPicture(String file, String userId); - Future createUser(String userId); + Future createUser(MyUser myUser); }