before rediense

This commit is contained in:
Felipe
2024-02-26 14:46:26 -05:00
parent 37b6665002
commit a1e367bdf3
10 changed files with 341 additions and 164 deletions
-2
View File
@@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:user_repository/user_repository.dart'; import 'package:user_repository/user_repository.dart';
+18 -12
View File
@@ -11,27 +11,33 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
ProfileBloc({required UserRepository userRepository}) ProfileBloc({required UserRepository userRepository})
: _userRepository = userRepository, : _userRepository = userRepository,
super(UpdateUserInfoInitial()) { super(UpdateUserInfoInitial()) {
on<UploadPicture>(_onUploadPicture); // on<UploadPicture>(_onUploadPicture);
on<UpdateUserInfo>(_onUpdateUserInfo); on<UpdateUserInfo>(_onUpdateUserInfo);
} }
void _onUploadPicture(UploadPicture event, Emitter<ProfileState> emit) async { // void _onUploadPicture(UploadPicture event, Emitter<ProfileState> emit) async {
emit(UploadPictureLoading()); // emit(UploadPictureLoading());
try { // try {
String userImage = // String userImage =
await _userRepository.uploadPicture(event.file, event.userId); // await _userRepository.uploadPicture(event.file, event.userId);
emit(UploadPictureSuccess(userImage)); // emit(UploadPictureSuccess(userImage));
} catch (e) { // } catch (e) {
emit(UploadPictureFailure()); // emit(UploadPictureFailure());
} // }
} // }
void _onUpdateUserInfo( void _onUpdateUserInfo(
UpdateUserInfo event, Emitter<ProfileState> emit) async { UpdateUserInfo event, Emitter<ProfileState> emit) async {
emit(UpdateUserInfoLoading()); emit(UpdateUserInfoLoading());
try { try {
await _userRepository.updateUserInfo(event.userId, { await _userRepository.updateUserInfo(event.userId, event.picture, {
'name': event.name, 'name': event.name,
// 'picture': event.picture,
'nickname': event.nickname,
'email': event.email,
'phone': event.phone,
'birthDate': event.birthDate,
'gender': event.gender
}); });
emit(const UpdateUserInfoSuccess()); emit(const UpdateUserInfoSuccess());
} catch (e) { } catch (e) {
+21 -5
View File
@@ -4,7 +4,7 @@ abstract class ProfileEvent extends Equatable {
const ProfileEvent(); const ProfileEvent();
@override @override
List<Object> get props => []; List<Object?> get props => [];
} }
class UploadPicture extends ProfileEvent { class UploadPicture extends ProfileEvent {
@@ -14,15 +14,31 @@ class UploadPicture extends ProfileEvent {
const UploadPicture(this.file, this.userId); const UploadPicture(this.file, this.userId);
@override @override
List<Object> get props => [file, userId]; List<Object?> get props => [file, userId];
} }
class UpdateUserInfo extends ProfileEvent { class UpdateUserInfo extends ProfileEvent {
final String userId; 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 @override
List<Object> get props => [userId, name]; List<Object?> get props =>
[userId, name, nickname, email, phone, picture, birthDate, gender];
} }
@@ -5,11 +5,10 @@ class BirthDatePicker extends StatefulWidget {
final TextEditingController controller; final TextEditingController controller;
const BirthDatePicker( const BirthDatePicker(
{Key? key, required this.onDateSelected, required this.controller}) {super.key, required this.onDateSelected, required this.controller});
: super(key: key);
@override @override
_BirthDatePickerState createState() => _BirthDatePickerState(); State<BirthDatePicker> createState() => _BirthDatePickerState();
} }
class _BirthDatePickerState extends State<BirthDatePicker> { class _BirthDatePickerState extends State<BirthDatePicker> {
@@ -39,10 +38,20 @@ class _BirthDatePickerState extends State<BirthDatePicker> {
_selectDate(context); _selectDate(context);
}, },
readOnly: true, readOnly: true,
decoration: const InputDecoration( decoration: InputDecoration(
prefixIcon: Icon(Icons.calendar_month), labelText: 'Fecha de nacimiento',
suffixIcon: Icon(Icons.arrow_drop_down), prefixIcon: const Icon(Icons.calendar_month_rounded),
hintText: 'Fecha de nacimiento', 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, controller: widget.controller,
); );
@@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
class GenderDropdown extends StatefulWidget { class GenderDropdown extends StatefulWidget {
final Function(String) onChanged; final Function(String) onChanged;
const GenderDropdown({Key? key, required this.onChanged}) : super(key: key); const GenderDropdown({super.key, required this.onChanged});
@override @override
_GenderDropdownState createState() => _GenderDropdownState(); State<GenderDropdown> createState() => _GenderDropdownState();
} }
class _GenderDropdownState extends State<GenderDropdown> { class _GenderDropdownState extends State<GenderDropdown> {
@@ -15,16 +15,24 @@ class _GenderDropdownState extends State<GenderDropdown> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InputDecorator( return InputDecorator(
decoration: const InputDecoration( decoration: InputDecoration(
border: UnderlineInputBorder(), border: OutlineInputBorder(
prefixIcon: Icon(Icons.people_outline), borderRadius: BorderRadius.circular(10.0),
contentPadding: EdgeInsets.symmetric(horizontal: 8.0), ),
prefixIcon: const Icon(Icons.groups),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 7.0,
),
), ),
child: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: DropdownButton<String>( child: DropdownButton<String>(
isExpanded: true, isExpanded: true,
value: selectedGender, value: selectedGender,
hint: const Text('Selecciona tu género'), hint: const Text(
'Selecciona tu género',
style: TextStyle(fontSize: 16.0),
),
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
selectedGender = newValue; selectedGender = newValue;
+6 -3
View File
@@ -26,10 +26,13 @@ class GeneralDrawerHeader extends StatelessWidget {
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
subtitle: Text( subtitle: Text(
context.read<MyUserBloc>().state.user!.email ?? '', context.read<MyUserBloc>().state.user!.email ??
context.read<MyUserBloc>().state.user!.phone ??
'',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
), ),
leading: context.read<MyUserBloc>().state.user!.picture == "" leading: context.read<MyUserBloc>().state.user!.picture == "" ||
context.read<MyUserBloc>().state.user!.picture == null
? Container( ? Container(
width: 80, width: 80,
height: 80, height: 80,
@@ -51,7 +54,7 @@ class GeneralDrawerHeader extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
image: DecorationImage( image: DecorationImage(
image: NetworkImage( image: NetworkImage(
context.read<MyUserBloc>().state.user!.picture!, context.read<MyUserBloc>().state.user?.picture ?? '',
), ),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
+10
View File
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class AboutScreen extends StatelessWidget {
const AboutScreen({super.key});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}
+182 -102
View File
@@ -1,9 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/profile_bloc/profile_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'; import 'package:prosappco/src/components/pop_appbar.dart';
class ProfileScreen extends StatefulWidget { class ProfileScreen extends StatefulWidget {
@@ -15,10 +21,19 @@ class ProfileScreen extends StatefulWidget {
class _ProfileScreenState extends State<ProfileScreen> { class _ProfileScreenState extends State<ProfileScreen> {
final TextEditingController _nameController = TextEditingController(); final TextEditingController _nameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _birthDateController = TextEditingController();
final TextEditingController _genderController = TextEditingController();
XFile? _imageFile;
@override @override
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
_emailController.dispose();
_phoneController.dispose();
_birthDateController.dispose();
_genderController.dispose();
super.dispose(); super.dispose();
} }
@@ -40,118 +55,183 @@ class _ProfileScreenState extends State<ProfileScreen> {
body: BlocBuilder<MyUserBloc, MyUserState>(builder: (context, state) { body: BlocBuilder<MyUserBloc, MyUserState>(builder: (context, state) {
if (state.status == MyUserStatus.success) { if (state.status == MyUserStatus.success) {
_nameController.text = state.user!.name ?? ''; _nameController.text = state.user!.name ?? '';
_emailController.text = state.user!.email ?? '';
_phoneController.text = state.user!.phone ?? '';
return Padding( return SingleChildScrollView(
padding: const EdgeInsets.all(20.0), child: Padding(
child: Column( padding: const EdgeInsets.all(20.0),
crossAxisAlignment: CrossAxisAlignment.stretch, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.stretch,
GestureDetector( children: [
onTap: () async { GestureDetector(
final ImagePicker picker = ImagePicker(); onTap: () async {
final XFile? image = await picker.pickImage( final ImagePicker picker = ImagePicker();
source: ImageSource.gallery, final XFile? image = await picker.pickImage(
maxHeight: 500, source: ImageSource.gallery,
maxWidth: 500, maxHeight: 500,
imageQuality: 40, maxWidth: 500,
); imageQuality: 40,
);
if (image != null) { if (image != null) {
context.read<ProfileBloc>().add( // context
UploadPicture(image.path, state.user!.id), // .read<ProfileBloc>()
); // .add(UploadPicture(image.path, state.user!.id));
} setState(() {
}, _imageFile = image;
child: state.user!.picture == "" });
? Container( }
width: 120, },
height: 120, child: state.user!.picture == "" ||
decoration: BoxDecoration( state.user!.picture == null
color: Colors.grey.shade300, ? _imageFile != null
shape: BoxShape.circle, ? Container(
), width: 150,
child: Icon( height: 150,
CupertinoIcons.person, decoration: BoxDecoration(
color: Colors.grey.shade400, color: Colors.grey,
size: 40, shape: BoxShape.circle,
), image: DecorationImage(
) image: FileImage(File(_imageFile!.path)),
: Container( fit: BoxFit.contain,
width: 150, ),
height: 150, ),
decoration: BoxDecoration( )
color: Colors.grey, : Container(
shape: BoxShape.circle, width: 120,
image: DecorationImage( height: 120,
image: NetworkImage( decoration: BoxDecoration(
context color: Colors.grey.shade300,
.read<MyUserBloc>() shape: BoxShape.circle,
.state ),
.user! child: Icon(
.picture!, 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<MyUserBloc>()
.state
.user
?.picture ??
''),
fit: BoxFit.contain,
), ),
fit: BoxFit.contain,
), ),
), ),
), ),
), const SizedBox(height: 20.0),
const SizedBox(height: 20.0), TextFormField(
TextFormField( controller: _nameController,
controller: _nameController, decoration: const InputDecoration(
decoration: const InputDecoration( labelText: 'Nombre',
labelText: 'Nombre', prefixIcon: Icon(Icons.person),
prefixIcon: Icon(Icons.person), hintText: 'Nombre (obligatorio)',
hintText: 'Nombre (obligatorio)', border: OutlineInputBorder(
border: OutlineInputBorder( borderRadius: BorderRadius.all(
borderRadius: BorderRadius.all( Radius.circular(10.0),
Radius.circular(25.0), )),
)), errorBorder: OutlineInputBorder(
errorBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.red),
borderSide: BorderSide(color: Colors.red), ),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 2.0),
),
), ),
focusedErrorBorder: OutlineInputBorder( validator: (value) {
borderSide: BorderSide(color: Colors.red, width: 2.0), 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) { const SizedBox(height: 20.0),
if (value == null || value.isEmpty) { TextFormField(
return 'Por favor, ingrese su nombre'; controller: _phoneController,
} decoration: const InputDecoration(
return null; labelText: 'Número de Teléfono',
}, prefixIcon: Icon(Icons.phone_android_rounded),
), hintText: '+57',
const SizedBox(height: 20.0), border: OutlineInputBorder(
TextFormField( borderRadius: BorderRadius.all(
decoration: const InputDecoration( Radius.circular(10.0),
labelText: 'Nombre', )),
prefixIcon: Icon(Icons.person), errorBorder: OutlineInputBorder(
hintText: 'Nombre (obligatorio)', borderSide: BorderSide(color: Colors.red),
border: OutlineInputBorder( ),
borderRadius: BorderRadius.all( focusedErrorBorder: OutlineInputBorder(
Radius.circular(25.0), borderSide: BorderSide(color: Colors.red, width: 2.0),
)), ),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 2.0),
), ),
), ),
validator: (value) { const SizedBox(height: 20.0),
if (value == null || value.isEmpty) { BirthDatePicker(
return 'Por favor, ingrese su nombre'; onDateSelected: (birthDay) {
} _birthDateController.text =
return null; DateFormat('dd/MM/yyyy').format(birthDay);
}, },
), controller: _birthDateController,
ElevatedButton( ),
onPressed: () { const SizedBox(height: 20.0),
context.read<ProfileBloc>().add( GenderDropdown(onChanged: (selectedGender) {
UpdateUserInfo(state.user!.id, _nameController.text)); _genderController.text = selectedGender;
}, }),
child: const Text('Guardar'), ElevatedButton(
), onPressed: () {
], context.read<ProfileBloc>().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 { } else {
@@ -19,7 +19,14 @@ class FirebaseUserRepository implements UserRepository {
FirebaseUserRepository(this._firebaseAuth) { FirebaseUserRepository(this._firebaseAuth) {
_firebaseAuth.userChanges().listen((user) async { _firebaseAuth.userChanges().listen((user) async {
if (user != null) { 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 { } else {
_userStreamController.add(null); _userStreamController.add(null);
} }
@@ -27,17 +34,35 @@ class FirebaseUserRepository implements UserRepository {
} }
Future<void> updateFromFirebase(String userId) async { Future<void> updateFromFirebase(String userId) async {
return updateFromFirebase2(userId: userId);
}
Future<void> updateFromFirebase2({
required String userId,
String? email,
String? name,
String? phone,
String? picture,
String? nickname,
}) async {
try { 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); _userStreamController.add(myUser);
} catch (e) { } catch (e) {
try { log('xd -- Error updating from firebase ${e.toString()}');
await createUser(userId); _userStreamController.add(null);
final myUser = await getMyUser(userId);
_userStreamController.add(myUser);
} catch (e) {
_userStreamController.add(null);
}
} }
} }
@@ -55,12 +80,13 @@ class FirebaseUserRepository implements UserRepository {
@override @override
Future<MyUser> signUp(MyUser myUser, String password) async { Future<MyUser> signUp(MyUser myUser, String password) async {
try { try {
UserCredential user = await _firebaseAuth.createUserWithEmailAndPassword( UserCredential userCredential =
await _firebaseAuth.createUserWithEmailAndPassword(
email: myUser.email!, email: myUser.email!,
password: password, password: password,
); );
myUser = myUser.copyWith(id: user.user!.uid); myUser = myUser.copyWith(id: userCredential.user!.uid);
return myUser; return myUser;
} catch (e) { } catch (e) {
@@ -102,6 +128,14 @@ class FirebaseUserRepository implements UserRepository {
var credentials = await _firebaseAuth.signInWithCredential( var credentials = await _firebaseAuth.signInWithCredential(
PhoneAuthProvider.credential( PhoneAuthProvider.credential(
verificationId: verificationId, smsCode: code)); 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; return credentials.user != null ? true : false;
} catch (e) { } catch (e) {
if (e is FirebaseAuthException) { if (e is FirebaseAuthException) {
@@ -165,11 +199,16 @@ class FirebaseUserRepository implements UserRepository {
// Get user data // Get user data
@override @override
Future<MyUser> getMyUser(String myUserId) async { Future<MyUser?> getMyUser(String myUserId) async {
try { try {
return usersCollection.doc(myUserId).get().then((value) { return usersCollection.doc(myUserId).get().then((value) {
final valueData = value.data();
if (valueData == null || valueData.isEmpty || !value.exists) {
return null;
}
return MyUser.fromEntity( return MyUser.fromEntity(
MyUserEntity.fromDocument(value.data()!), MyUserEntity.fromDocument(valueData),
); );
}); });
} catch (e) { } catch (e) {
@@ -179,8 +218,22 @@ class FirebaseUserRepository implements UserRepository {
} }
@override @override
Future<void> updateUserInfo(String userId, Map<String, dynamic> data) async { Future<void> updateUserInfo(
String userId, String? picture, Map<String, dynamic> data) async {
try { 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 usersCollection.doc(userId).update(data);
await updateFromFirebase(userId); await updateFromFirebase(userId);
@@ -209,16 +262,9 @@ class FirebaseUserRepository implements UserRepository {
} }
@override @override
Future<void> createUser(String userId) async { Future<void> createUser(MyUser myUser) async {
try { try {
await usersCollection.doc(userId).set({ await usersCollection.doc(myUser.id).set(myUser.toEntity().toDocument());
'id': userId,
'name': '',
'nickname': '',
'email': '',
'phone': '',
'picture': '',
});
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
} }
@@ -20,11 +20,12 @@ abstract class UserRepository {
Future<void> setUserData(MyUser user); Future<void> setUserData(MyUser user);
Future<MyUser> getMyUser(String myUserId); Future<MyUser?> getMyUser(String myUserId);
Future<void> updateUserInfo(String userId, Map<String, dynamic> data); Future<void> updateUserInfo(
String userId, String picture, Map<String, dynamic> data);
Future<String> uploadPicture(String file, String userId); Future<String> uploadPicture(String file, String userId);
Future<void> createUser(String userId); Future<void> createUser(MyUser myUser);
} }