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:equatable/equatable.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})
: _userRepository = userRepository,
super(UpdateUserInfoInitial()) {
on<UploadPicture>(_onUploadPicture);
// on<UploadPicture>(_onUploadPicture);
on<UpdateUserInfo>(_onUpdateUserInfo);
}
void _onUploadPicture(UploadPicture event, Emitter<ProfileState> 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<ProfileState> 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<ProfileState> 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) {
+21 -5
View File
@@ -4,7 +4,7 @@ abstract class ProfileEvent extends Equatable {
const ProfileEvent();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
class UploadPicture extends ProfileEvent {
@@ -14,15 +14,31 @@ class UploadPicture extends ProfileEvent {
const UploadPicture(this.file, this.userId);
@override
List<Object> get props => [file, userId];
List<Object?> 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<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;
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<BirthDatePicker> createState() => _BirthDatePickerState();
}
class _BirthDatePickerState extends State<BirthDatePicker> {
@@ -39,10 +38,20 @@ class _BirthDatePickerState extends State<BirthDatePicker> {
_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,
);
@@ -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<GenderDropdown> createState() => _GenderDropdownState();
}
class _GenderDropdownState extends State<GenderDropdown> {
@@ -15,16 +15,24 @@ class _GenderDropdownState extends State<GenderDropdown> {
@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<String>(
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;
+6 -3
View File
@@ -26,10 +26,13 @@ class GeneralDrawerHeader extends StatelessWidget {
style: const TextStyle(fontWeight: FontWeight.bold),
),
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),
),
leading: context.read<MyUserBloc>().state.user!.picture == ""
leading: context.read<MyUserBloc>().state.user!.picture == "" ||
context.read<MyUserBloc>().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<MyUserBloc>().state.user!.picture!,
context.read<MyUserBloc>().state.user?.picture ?? '',
),
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: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<ProfileScreen> {
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<ProfileScreen> {
body: BlocBuilder<MyUserBloc, MyUserState>(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<ProfileBloc>().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<MyUserBloc>()
.state
.user!
.picture!,
if (image != null) {
// context
// .read<ProfileBloc>()
// .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<MyUserBloc>()
.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<ProfileBloc>().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<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 {
@@ -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<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 {
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<MyUser> 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<MyUser> getMyUser(String myUserId) async {
Future<MyUser?> 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<void> updateUserInfo(String userId, Map<String, dynamic> data) async {
Future<void> updateUserInfo(
String userId, String? picture, Map<String, dynamic> 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<void> createUser(String userId) async {
Future<void> 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());
}
@@ -20,11 +20,12 @@ abstract class UserRepository {
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<void> createUser(String userId);
Future<void> createUser(MyUser myUser);
}