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();
}
}
+102 -22
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,8 +55,11 @@ 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(
child: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -57,13 +75,30 @@ class _ProfileScreenState extends State<ProfileScreen> {
); );
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 == "" child: state.user!.picture == "" ||
state.user!.picture == null
? _imageFile != null
? Container( ? 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, width: 120,
height: 120, height: 120,
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -83,13 +118,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
color: Colors.grey, color: Colors.grey,
shape: BoxShape.circle, shape: BoxShape.circle,
image: DecorationImage( image: DecorationImage(
image: NetworkImage( image: NetworkImage(context
context
.read<MyUserBloc>() .read<MyUserBloc>()
.state .state
.user! .user
.picture!, ?.picture ??
), ''),
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@@ -104,7 +138,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
hintText: 'Nombre (obligatorio)', hintText: 'Nombre (obligatorio)',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(25.0), Radius.circular(10.0),
)), )),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red), borderSide: BorderSide(color: Colors.red),
@@ -122,13 +156,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
const SizedBox(height: 20.0), const SizedBox(height: 20.0),
TextFormField( TextFormField(
controller: _emailController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Nombre', labelText: 'Email',
prefixIcon: Icon(Icons.person), prefixIcon: Icon(Icons.email_rounded),
hintText: 'Nombre (obligatorio)', hintText: 'Email',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(25.0), Radius.circular(10.0),
)), )),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red), borderSide: BorderSide(color: Colors.red),
@@ -137,22 +172,67 @@ class _ProfileScreenState extends State<ProfileScreen> {
borderSide: BorderSide(color: Colors.red, width: 2.0), 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: _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),
),
),
),
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( ElevatedButton(
onPressed: () { onPressed: () {
context.read<ProfileBloc>().add( context.read<ProfileBloc>().add(
UpdateUserInfo(state.user!.id, _nameController.text)); 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'), child: const Text('Guardar'),
), ),
], ],
), ),
),
); );
} else { } else {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
@@ -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 {
try { return updateFromFirebase2(userId: userId);
final 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);
} }
Future<void> updateFromFirebase2({
required String userId,
String? email,
String? name,
String? phone,
String? picture,
String? nickname,
}) async {
try {
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) {
log('xd -- Error updating from firebase ${e.toString()}');
_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);
} }