From 11d9ee6d351932675b6a678fb60ae7b6913215bf Mon Sep 17 00:00:00 2001 From: Felipe Date: Mon, 26 Feb 2024 16:32:31 -0500 Subject: [PATCH] after rediense --- lib/blocs/profile_bloc/profile_bloc.dart | 19 +- lib/blocs/profile_bloc/profile_event.dart | 33 +-- lib/blocs/profile_bloc/profile_state.dart | 13 - ..._date_picker.dart => birthday_picker.dart} | 8 +- lib/components/general_drawer_header.dart | 75 +++--- lib/screens/profile/profile_screen.dart | 246 +++++++++++------- .../authentication_repository.dart | 8 +- .../lib/src/entities/my_user_entity.dart | 23 +- .../lib/src/models/my_user.dart | 23 +- .../firebase_user_repository.dart | 28 +- .../lib/src/repositories/user_repo.dart | 3 +- 11 files changed, 256 insertions(+), 223 deletions(-) rename lib/components/{birth_date_picker.dart => birthday_picker.dart} (88%) diff --git a/lib/blocs/profile_bloc/profile_bloc.dart b/lib/blocs/profile_bloc/profile_bloc.dart index 469fb8d..85744b7 100644 --- a/lib/blocs/profile_bloc/profile_bloc.dart +++ b/lib/blocs/profile_bloc/profile_bloc.dart @@ -30,15 +30,16 @@ class ProfileBloc extends Bloc { UpdateUserInfo event, Emitter emit) async { emit(UpdateUserInfoLoading()); try { - 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 - }); + final userImageUrl = event.filePicture != null + ? await _userRepository.uploadPicture( + event.filePicture!, event.myUser.id) + : null; + + final myUser = userImageUrl == null + ? event.myUser + : event.myUser.copyWith(picture: userImageUrl); + + await _userRepository.updateUserInfo(myUser); emit(const UpdateUserInfoSuccess()); } catch (e) { emit(UpdateUserInfoFailure()); diff --git a/lib/blocs/profile_bloc/profile_event.dart b/lib/blocs/profile_bloc/profile_event.dart index d753a46..48d2db9 100644 --- a/lib/blocs/profile_bloc/profile_event.dart +++ b/lib/blocs/profile_bloc/profile_event.dart @@ -7,38 +7,15 @@ abstract class ProfileEvent extends Equatable { List get props => []; } -class UploadPicture extends ProfileEvent { - final String file; - final String userId; - - const UploadPicture(this.file, this.userId); - - @override - List get props => [file, userId]; -} - class UpdateUserInfo extends ProfileEvent { - final String userId; - final String picture; - final String? name; - final String? nickname; - final String? email; - final String? phone; - final String? birthDate; - final String? gender; + final MyUser myUser; + final String? filePicture; const UpdateUserInfo({ - required this.userId, - required this.picture, - this.name, - this.nickname, - this.email, - this.phone, - this.birthDate, - this.gender, + required this.myUser, + this.filePicture, }); @override - List get props => - [userId, name, nickname, email, phone, picture, birthDate, gender]; + List get props => [myUser, filePicture]; } diff --git a/lib/blocs/profile_bloc/profile_state.dart b/lib/blocs/profile_bloc/profile_state.dart index edc0c1d..9be39e9 100644 --- a/lib/blocs/profile_bloc/profile_state.dart +++ b/lib/blocs/profile_bloc/profile_state.dart @@ -9,19 +9,6 @@ abstract class ProfileState extends Equatable { class UpdateUserInfoInitial extends ProfileState {} -class UploadPictureFailure extends ProfileState {} - -class UploadPictureLoading extends ProfileState {} - -class UploadPictureSuccess extends ProfileState { - final String userImage; - - const UploadPictureSuccess(this.userImage); - - @override - List get props => [userImage]; -} - class UpdateUserInfoFailure extends ProfileState {} class UpdateUserInfoLoading extends ProfileState {} diff --git a/lib/components/birth_date_picker.dart b/lib/components/birthday_picker.dart similarity index 88% rename from lib/components/birth_date_picker.dart rename to lib/components/birthday_picker.dart index 06c57bd..97c768c 100644 --- a/lib/components/birth_date_picker.dart +++ b/lib/components/birthday_picker.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; -class BirthDatePicker extends StatefulWidget { +class BirthdayPicker extends StatefulWidget { final Function(DateTime) onDateSelected; final TextEditingController controller; - const BirthDatePicker( + const BirthdayPicker( {super.key, required this.onDateSelected, required this.controller}); @override - State createState() => _BirthDatePickerState(); + State createState() => _BirthdayPickerState(); } -class _BirthDatePickerState extends State { +class _BirthdayPickerState extends State { DateTime selectedDate = DateTime.now().subtract(const Duration(days: 365 * 20)); diff --git a/lib/components/general_drawer_header.dart b/lib/components/general_drawer_header.dart index ca78cf6..952055d 100644 --- a/lib/components/general_drawer_header.dart +++ b/lib/components/general_drawer_header.dart @@ -9,6 +9,7 @@ class GeneralDrawerHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final user = context.read().state.user!; return ListTile( onTap: () { Navigator.pop(context); @@ -22,46 +23,54 @@ class GeneralDrawerHeader extends StatelessWidget { ); }, title: Text( - context.read().state.user!.name ?? '', + user.name ?? '', style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( - context.read().state.user!.email ?? - context.read().state.user!.phone ?? - '', + user.drawerLabel, style: const TextStyle(fontSize: 12), ), - leading: context.read().state.user!.picture == "" || - context.read().state.user!.picture == null - ? Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, - ), - child: Icon( - CupertinoIcons.person, - color: Colors.grey.shade400, - size: 35, - ), - ) - : Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Colors.grey, - shape: BoxShape.circle, - image: DecorationImage( - image: NetworkImage( - context.read().state.user?.picture ?? '', - ), - fit: BoxFit.cover, - ), - ), - ), + leading: pictureWidget(user.picture, context), trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black), contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), ); } + + Widget pictureWidget(String? pictureUrl, BuildContext context) { + ImageProvider? imageProvider; + + if (pictureUrl != null && pictureUrl.isNotEmpty) { + imageProvider = NetworkImage(pictureUrl); + } + + return pictureContainerWidget(imageProvider); + } + + Widget pictureContainerWidget(ImageProvider? imageProvider) { + final image = imageProvider == null + ? null + : DecorationImage( + image: imageProvider, + fit: BoxFit.contain, + ); + + final widget = image == null + ? Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 40, + ) + : null; + + return Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + image: image, + ), + child: widget, + ); + } } diff --git a/lib/screens/profile/profile_screen.dart b/lib/screens/profile/profile_screen.dart index a3edc67..1d9ac59 100644 --- a/lib/screens/profile/profile_screen.dart +++ b/lib/screens/profile/profile_screen.dart @@ -7,9 +7,8 @@ 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/birthday_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 { @@ -23,16 +22,17 @@ class _ProfileScreenState extends State { final TextEditingController _nameController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _phoneController = TextEditingController(); - final TextEditingController _birthDateController = TextEditingController(); + final TextEditingController _birthdayController = TextEditingController(); final TextEditingController _genderController = TextEditingController(); XFile? _imageFile; + bool isLoading = false; @override void dispose() { _nameController.dispose(); _emailController.dispose(); _phoneController.dispose(); - _birthDateController.dispose(); + _birthdayController.dispose(); _genderController.dispose(); super.dispose(); } @@ -41,8 +41,25 @@ class _ProfileScreenState extends State { Widget build(BuildContext context) { return BlocListener( listener: (context, state) { - if (state is UploadPictureSuccess) { - setState(() {}); + if (state is UpdateUserInfoLoading) { + setState(() { + isLoading = true; + }); + } else if (state is UpdateUserInfoSuccess) { + // Snack bar + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Información actualizada'), + )); + setState(() { + isLoading = false; + }); + } else if (state is UpdateUserInfoFailure) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Error al actualizar la información'), + )); + setState(() { + isLoading = false; + }); } }, child: Scaffold( @@ -60,75 +77,12 @@ class _ProfileScreenState extends State { return SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(20.0), + padding: + const EdgeInsets.symmetric(horizontal: 40, vertical: 20), 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)); - 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, - ), - ), - ), - ), + pictureWidget(state, context), const SizedBox(height: 20.0), TextFormField( controller: _nameController, @@ -193,43 +147,19 @@ class _ProfileScreenState extends State { ), ), const SizedBox(height: 20.0), - BirthDatePicker( + BirthdayPicker( onDateSelected: (birthDay) { - _birthDateController.text = + _birthdayController.text = DateFormat('dd/MM/yyyy').format(birthDay); }, - controller: _birthDateController, + controller: _birthdayController, ), 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'), - ), + const SizedBox(height: 60.0), + saveButton(state, context), ], ), ), @@ -241,4 +171,118 @@ class _ProfileScreenState extends State { ), ); } + + Widget saveButton(MyUserState state, BuildContext context) { + return ElevatedButton( + onPressed: () { + if (isLoading) { + return; + } + + final myUser = state.user!.copyWith( + name: _nameController.text, + email: _emailController.text, + phone: _phoneController.text, + birthday: _birthdayController.text, + gender: _genderController.text, + nickname: _nameController.text.trim().toLowerCase(), + ); + + context.read().add( + UpdateUserInfo( + myUser: myUser, + filePicture: _imageFile?.path, + ), + ); + + // Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, // Color de fondo + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), // Radio de borde + ), + shadowColor: Colors.grey, // Sombra + elevation: 5, // Elevación + ), + child: Container( + constraints: const BoxConstraints( + maxWidth: 300.0, minHeight: 50.0), // Ajustes de tamaño + alignment: Alignment.center, + child: isLoading + ? const CircularProgressIndicator( + color: Colors.white, + ) + : const Text( + 'Actualizar', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, // Texto audaz + ), + ), + ), + ); + } + + Widget pictureWidget(MyUserState state, BuildContext context) { + final pictureUrl = state.user?.picture; + final pathImageFile = _imageFile?.path; + + ImageProvider? imageProvider; + + if (pathImageFile != null && pathImageFile.isNotEmpty) { + imageProvider = FileImage(File(pathImageFile)); + } else if (pictureUrl != null && pictureUrl.isNotEmpty) { + imageProvider = NetworkImage(pictureUrl); + } + + return 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) { + setState(() { + _imageFile = image; + }); + } + }, + child: pictureContainerWidget(imageProvider), + ); + } + + Widget pictureContainerWidget(ImageProvider? imageProvider) { + final image = imageProvider == null + ? null + : DecorationImage( + image: imageProvider, + fit: BoxFit.contain, + ); + + final widget = image == null + ? Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 40, + ) + : null; + + return Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + image: image, + ), + child: widget, + ); + } } diff --git a/lib/src/authentication/authentication_repository.dart b/lib/src/authentication/authentication_repository.dart index e51b73e..dcfe850 100644 --- a/lib/src/authentication/authentication_repository.dart +++ b/lib/src/authentication/authentication_repository.dart @@ -210,16 +210,16 @@ class AuthenticationRepository extends GetxController { } Future getBirthday(String uid) async { - String birthDate = ''; + String birthday = ''; try { final snapshot = await FirebaseFirestore.instance.collection('users').doc(uid).get(); final Map? data = snapshot.data(); - birthDate = data?['birth_date'] ?? ''; + birthday = data?['birth_date'] ?? ''; } catch (e) { - print('Error getting birthDate: $e'); + print('Error getting birthday: $e'); } - return birthDate; + return birthday; } Future getCoordsOfCity(String uid) async { diff --git a/packages/user_repository/lib/src/entities/my_user_entity.dart b/packages/user_repository/lib/src/entities/my_user_entity.dart index 255a58b..7578640 100644 --- a/packages/user_repository/lib/src/entities/my_user_entity.dart +++ b/packages/user_repository/lib/src/entities/my_user_entity.dart @@ -7,14 +7,18 @@ class MyUserEntity extends Equatable { final String? name; final String? nickname; final String? picture; + final String? birthday; + final String? gender; const MyUserEntity({ required this.id, - this.email, - this.phone, - this.name, - this.nickname, - this.picture, + required this.email, + required this.phone, + required this.name, + required this.nickname, + required this.picture, + required this.birthday, + required this.gender, }); Map toDocument() { @@ -25,6 +29,8 @@ class MyUserEntity extends Equatable { 'name': name, 'nickname': name?.trim().toLowerCase(), 'picture': picture, + 'birthday': birthday, + 'gender': gender, }; } @@ -36,11 +42,14 @@ class MyUserEntity extends Equatable { name: doc['name'] as String?, nickname: doc['nickname'] as String?, picture: doc['picture'] as String?, + birthday: doc['birthday'] as String?, + gender: doc['gender'] as String?, ); } @override - List get props => [id, email, phone, name, nickname, picture]; + List get props => + [id, email, phone, name, nickname, picture, birthday, gender]; @override String toString() { @@ -51,6 +60,8 @@ class MyUserEntity extends Equatable { name: $name nickname: $nickname picture: $picture + birthday: $birthday + gender: $gender }'''; } } diff --git a/packages/user_repository/lib/src/models/my_user.dart b/packages/user_repository/lib/src/models/my_user.dart index 53b3f0b..3387941 100644 --- a/packages/user_repository/lib/src/models/my_user.dart +++ b/packages/user_repository/lib/src/models/my_user.dart @@ -9,6 +9,8 @@ class MyUser extends Equatable { final String? name; final String? nickname; final String? picture; + final String? birthday; + final String? gender; const MyUser({ required this.id, @@ -17,8 +19,16 @@ class MyUser extends Equatable { this.name, this.nickname, this.picture, + this.birthday, + this.gender, }); + get drawerLabel => email != null && email!.isNotEmpty + ? email! + : phone != null && phone!.isNotEmpty + ? phone! + : "N/A"; + /// Empty user which represents an unauthenticated user. static const empty = MyUser( id: '', @@ -27,6 +37,8 @@ class MyUser extends Equatable { name: '', nickname: '', picture: '', + birthday: '', + gender: '', ); /// Modify MyUser parameters @@ -37,6 +49,8 @@ class MyUser extends Equatable { String? name, String? nickname, String? picture, + String? birthday, + String? gender, }) { return MyUser( id: id ?? this.id, @@ -45,6 +59,8 @@ class MyUser extends Equatable { name: name ?? this.name, nickname: nickname ?? this.nickname, picture: picture ?? this.picture, + birthday: birthday ?? this.birthday, + gender: gender ?? this.gender, ); } @@ -62,6 +78,8 @@ class MyUser extends Equatable { name: name, nickname: nickname, picture: picture, + birthday: birthday, + gender: gender, ); } @@ -73,9 +91,12 @@ class MyUser extends Equatable { name: entity.name, nickname: entity.nickname, picture: entity.picture, + birthday: entity.birthday, + gender: entity.gender, ); } @override - List get props => [id, email, phone, name, nickname, picture]; + List get props => + [id, email, phone, name, nickname, picture, birthday, gender]; } 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 40678b3..d8aac43 100644 --- a/packages/user_repository/lib/src/repositories/firebase_user_repository.dart +++ b/packages/user_repository/lib/src/repositories/firebase_user_repository.dart @@ -218,25 +218,12 @@ class FirebaseUserRepository implements UserRepository { } @override - Future updateUserInfo( - String userId, String? picture, Map data) async { + Future updateUserInfo(MyUser myUser) 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); + await usersCollection + .doc(myUser.id) + .update(myUser.toEntity().toDocument()); + await updateFromFirebase(myUser.id); } catch (e) { log(e.toString()); rethrow; @@ -249,11 +236,8 @@ class FirebaseUserRepository implements UserRepository { File imageFile = File(file); Reference firebaseStoreRef = FirebaseStorage.instance.ref().child('$userId/PP/${userId}_lead'); - await firebaseStoreRef.putFile( - imageFile, - ); + await firebaseStoreRef.putFile(imageFile); String url = await firebaseStoreRef.getDownloadURL(); - await usersCollection.doc(userId).update({'picture': url}); return url; } 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 04053b4..f4f256a 100644 --- a/packages/user_repository/lib/src/repositories/user_repo.dart +++ b/packages/user_repository/lib/src/repositories/user_repo.dart @@ -22,8 +22,7 @@ abstract class UserRepository { Future getMyUser(String myUserId); - Future updateUserInfo( - String userId, String picture, Map data); + Future updateUserInfo(MyUser myUser); Future uploadPicture(String file, String userId);