This commit is contained in:
Felipe
2024-02-19 18:16:51 -05:00
parent 9e6f76e309
commit 997c6047bb
34 changed files with 1235 additions and 594 deletions
+7 -7
View File
@@ -25,16 +25,16 @@ class MainApp extends StatelessWidget {
BlocProvider<UpdateUserInfoBloc>( BlocProvider<UpdateUserInfoBloc>(
create: (context) => Injector.appInstance.get<UpdateUserInfoBloc>(), create: (context) => Injector.appInstance.get<UpdateUserInfoBloc>(),
), ),
BlocProvider<SignInBloc>( // BlocProvider<SignInBloc>(
create: (context) => Injector.appInstance.get<SignInBloc>(), // create: (context) => Injector.appInstance.get<SignInBloc>(),
), // ),
BlocProvider<SignUpBloc>( // BlocProvider<SignUpBloc>(
create: (context) => Injector.appInstance.get<SignUpBloc>(), // create: (context) => Injector.appInstance.get<SignUpBloc>(),
) // ),
], ],
child: BlocBuilder<MyUserBloc, MyUserState>( child: BlocBuilder<MyUserBloc, MyUserState>(
builder: (context, state) { builder: (context, state) {
return const MyAppView(); return const SafeArea(child: MyAppView());
}, },
), ),
); );
+24 -19
View File
@@ -16,29 +16,34 @@ class MyAppView extends StatelessWidget {
title: 'Prosappco', title: 'Prosappco',
theme: ThemeData( theme: ThemeData(
colorScheme: const ColorScheme.light( colorScheme: const ColorScheme.light(
background: Colors.white, background: Colors.white,
onBackground: Colors.black, onBackground: Colors.black,
primary: Color.fromRGBO(66, 164, 239, 1), primary: Color.fromRGBO(66, 164, 239, 1),
onPrimary: Colors.black, onPrimary: Colors.black,
secondary: Color.fromRGBO(35, 108, 244, 1), secondary: Color.fromRGBO(35, 108, 244, 1),
onSecondary: Colors.white, onSecondary: Colors.white,
tertiary: Color.fromRGBO(255, 204, 128, 1), tertiary: Color.fromRGBO(214, 244, 255, 1),
error: Colors.red, error: Colors.red,
outline: Color(0xFF424242)), outline: Color(0xFF424242),
),
), ),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>( home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) { builder: (context, state) {
switch (state.status) { return getScreen(state);
case AuthenticationStatus.authenticated:
return const HomeScreen();
case AuthenticationStatus.unauthenticated:
return const WelcomeScreen();
case AuthenticationStatus.unknown:
return const SplashScreen();
}
}), }),
); );
} }
getScreen(AuthenticationState state) {
switch (state.status) {
case AuthenticationStatus.authenticated:
return const HomeScreen();
case AuthenticationStatus.unauthenticated:
return WelcomeScreen();
case AuthenticationStatus.unknown:
return const SplashScreen();
}
}
} }
@@ -19,13 +19,23 @@ class AuthenticationBloc
_userSubscription = userRepository.streamUser().listen((authUser) { _userSubscription = userRepository.streamUser().listen((authUser) {
add(AuthenticationUserChanged(authUser)); add(AuthenticationUserChanged(authUser));
}); });
on<AuthenticationUserChanged>((event, emit) { on<AuthenticationUserChanged>(_onAuthenticationUserChanged);
if (event.user != null) { on<AuthenticationLogoutRequested>(_onAuthenticationLogoutRequested);
emit(AuthenticationState.authenticated(event.user!)); }
} else {
emit(const AuthenticationState.unauthenticated()); void _onAuthenticationUserChanged(
} AuthenticationUserChanged event, Emitter<AuthenticationState> emit) {
}); emit(
event.user != null
? AuthenticationState.authenticated(event.user!)
: const AuthenticationState.unauthenticated(),
);
}
void _onAuthenticationLogoutRequested(AuthenticationLogoutRequested event,
Emitter<AuthenticationState> emit) async {
await userRepository.logOut();
emit(const AuthenticationState.unauthenticated());
} }
@override @override
+13 -10
View File
@@ -11,15 +11,18 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
SignUpBloc({required UserRepository userRepository}) SignUpBloc({required UserRepository userRepository})
: _userRepository = userRepository, : _userRepository = userRepository,
super(SignUpInitial()) { super(SignUpInitial()) {
on<SignUpRequired>((event, emit) async { on<SignUpRequired>(_onSignUpRequired);
emit(SignUpProcess()); }
try {
MyUser user = await _userRepository.signUp(event.user, event.password); void _onSignUpRequired(
await _userRepository.setUserData(user); SignUpRequired event, Emitter<SignUpState> emit) async {
emit(SignUpSuccess()); emit(SignUpProcess());
} catch (e) { try {
emit(SignUpFailure()); MyUser user = await _userRepository.signUp(event.user, event.password);
} await _userRepository.setUserData(user);
}); emit(SignUpSuccess());
} catch (e) {
emit(SignUpFailure());
}
} }
} }
+25 -8
View File
@@ -14,18 +14,35 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
SignInBloc({required UserRepository userRepository}) SignInBloc({required UserRepository userRepository})
: _userRepository = userRepository, : _userRepository = userRepository,
super(SignInInitial()) { super(SignInInitial()) {
on<SignInRequired>((event, emit) async { userRepository.streamUser().listen((authUser) {
emit(SignInProcess());
try { try {
await _userRepository.signIn(event.email, event.password); if (authUser == null) {
emit(SignInSuccess()); emit(SignInFailure());
} else {
emit(SignInSuccess());
}
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
emit(const SignInFailure());
} }
}); });
on<SignOutRequired>((event, emit) async { on<SignInRequired>(_onSignInRequired);
await _userRepository.logOut(); on<SignOutRequired>(_onSignOutRequired);
}); }
void _onSignInRequired(
SignInRequired event, Emitter<SignInState> emit) async {
emit(SignInProcess());
try {
await _userRepository.signIn(event.email, event.password);
emit(SignInSuccess());
} catch (e) {
log(e.toString());
emit(const SignInFailure());
}
}
void _onSignOutRequired(
SignOutRequired event, Emitter<SignInState> emit) async {
await _userRepository.logOut();
} }
} }
+75 -80
View File
@@ -1,96 +1,91 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/update_user_info_bloc/update_user_info_bloc.dart';
import 'package:prosappco/components/general_drawer_header.dart'; import 'package:prosappco/components/general_drawer_header.dart';
import 'package:prosappco/components/general_drawer_item.dart'; import 'package:prosappco/components/general_drawer_item.dart';
import 'package:prosappco/screens/configuration/configuration_screen.dart';
class GeneralDrawer extends StatefulWidget { class GeneralDrawer extends StatelessWidget {
const GeneralDrawer({super.key}); const GeneralDrawer({super.key});
@override
State<GeneralDrawer> createState() => _GeneralDrawerState();
}
class _GeneralDrawerState extends State<GeneralDrawer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>( return Drawer(
listener: (context, state) { backgroundColor: Theme.of(context).colorScheme.background,
if (state is UploadPictureSuccess) { child: Column(
setState(() { crossAxisAlignment: CrossAxisAlignment.stretch,
context.read<MyUserBloc>().state.user!.picture = state.userImage; children: [
}); const GeneralDrawerHeader(),
} Divider(
}, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
child: Drawer( thickness: 0.5,
backgroundColor: Theme.of(context).colorScheme.background, height: 1,
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.stretch, Expanded(
children: [ child: SingleChildScrollView(
const GeneralDrawerHeader(), child: Column(
Divider( children: [
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1), GeneralDrawerItem(
thickness: 0.5, leading: Icons.history,
height: 1, label: 'Mis servicios',
), onTap: () {
Expanded( Navigator.pop(context);
child: SingleChildScrollView( },
child: Column( ),
children: [ GeneralDrawerItem(
GeneralDrawerItem( leading: Icons.settings_outlined,
icon: Icons.person_outline, label: 'Configuración',
label: 'Mi Perfil', onTap: () {
onTap: () { Navigator.pop(context);
Navigator.pop(context); Navigator.push(
}, context,
), CupertinoPageRoute(
], builder: (context) => const ConfigurationScreen(),
), ),
);
},
),
GeneralDrawerItem(
leading: Icons.help_outline,
label: 'Soporte',
onTap: () {
Navigator.pop(context);
},
),
GeneralDrawerItem(
leading: Icons.campaign_outlined,
label: 'Sugerencias',
onTap: () {
Navigator.pop(context);
},
),
],
), ),
), ),
Divider( ),
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1), Divider(
thickness: 0.5, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
height: 1, thickness: 0.5,
), height: 1,
const SizedBox(height: 15), ),
Padding( const SizedBox(height: 15),
padding: const EdgeInsets.symmetric(horizontal: 20), Padding(
child: ElevatedButton( padding: const EdgeInsets.symmetric(horizontal: 20),
onPressed: () {}, child: ElevatedButton(
style: ElevatedButton.styleFrom( onPressed: () {},
backgroundColor: Theme.of(context).colorScheme.primary, style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15), backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder( padding: const EdgeInsets.symmetric(vertical: 15),
borderRadius: BorderRadius.circular(10), shape: RoundedRectangleBorder(
)), borderRadius: BorderRadius.circular(10),
child: const Text( )),
'Modo Profesional', child: const Text(
style: TextStyle(color: Colors.white, fontSize: 18), 'Modo Profesional',
), style: TextStyle(color: Colors.white, fontSize: 18),
), ),
), ),
const SizedBox(height: 15), ),
// Container( const SizedBox(height: 15),
// height: MediaQuery.of(context).size.height, ],
// color: Theme.of(context).colorScheme.background,
// child: ListView(
// children: [
// ListTile(
// onTap: () {
// context.read<SignInBloc>().add(const SignOutRequired());
// },
// title: const Text(
// 'Cerrar Sesión',
// ),
// )
// ],
// ),
// )
],
),
), ),
); );
} }
+47 -62
View File
@@ -2,78 +2,63 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.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/update_user_info_bloc/update_user_info_bloc.dart';
import 'package:prosappco/screens/profile/profile_screen.dart'; import 'package:prosappco/screens/profile/profile_screen.dart';
class GeneralDrawerHeader extends StatefulWidget { class GeneralDrawerHeader extends StatelessWidget {
const GeneralDrawerHeader({super.key}); const GeneralDrawerHeader({super.key});
@override
State<GeneralDrawerHeader> createState() => _GeneralDrawerHeaderState();
}
class _GeneralDrawerHeaderState extends State<GeneralDrawerHeader> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>( return ListTile(
listener: (context, state) { onTap: () {
if (state is UploadPictureSuccess) { Navigator.pop(context);
setState(() { Navigator.push(
context.read<MyUserBloc>().state.user!.picture = state.userImage; context,
}); CupertinoPageRoute(
} builder: (BuildContext context) {
return const ProfileScreen();
},
),
);
}, },
child: ListTile( title: Text(
onTap: () { context.read<MyUserBloc>().state.user!.name,
Navigator.push( style: const TextStyle(fontWeight: FontWeight.bold),
context, ),
CupertinoPageRoute( subtitle: Text(
builder: (BuildContext context) { context.read<MyUserBloc>().state.user!.email,
return const ProfileScreen(); style: const TextStyle(fontSize: 12),
}, ),
), leading: context.read<MyUserBloc>().state.user!.picture == ""
); ? Container(
}, width: 80,
title: Text( height: 80,
context.read<MyUserBloc>().state.user!.name, decoration: BoxDecoration(
style: const TextStyle(fontWeight: FontWeight.bold), color: Colors.grey.shade300,
), shape: BoxShape.circle,
subtitle: Text( ),
context.read<MyUserBloc>().state.user!.email, child: Icon(
style: const TextStyle(fontSize: 12), CupertinoIcons.person,
), color: Colors.grey.shade400,
leading: context.read<MyUserBloc>().state.user!.picture == "" size: 35,
? Container( ),
width: 80, )
height: 80, : Container(
decoration: BoxDecoration( width: 60,
color: Colors.grey.shade300, height: 60,
shape: BoxShape.circle, decoration: BoxDecoration(
), color: Colors.grey,
child: Icon( shape: BoxShape.circle,
CupertinoIcons.person, image: DecorationImage(
color: Colors.grey.shade400, image: NetworkImage(
size: 35, context.read<MyUserBloc>().state.user!.picture!,
),
)
: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.grey,
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
context.read<MyUserBloc>().state.user!.picture!,
),
fit: BoxFit.cover,
), ),
fit: BoxFit.cover,
), ),
), ),
trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black), ),
contentPadding: trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black),
const EdgeInsets.symmetric(vertical: 10, horizontal: 15), contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
),
); );
} }
} }
+19 -7
View File
@@ -2,13 +2,17 @@ import 'package:flutter/material.dart';
class GeneralDrawerItem extends StatelessWidget { class GeneralDrawerItem extends StatelessWidget {
final String label; final String label;
final IconData icon; final IconData? leading;
final bool trailing;
final Color? color;
final VoidCallback onTap; final VoidCallback onTap;
const GeneralDrawerItem({ const GeneralDrawerItem({
super.key, super.key,
required this.label, required this.label,
required this.icon, this.leading,
this.trailing = false,
this.color,
required this.onTap, required this.onTap,
}); });
@@ -16,13 +20,21 @@ class GeneralDrawerItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
onTap: onTap, onTap: onTap,
leading: Icon( leading: leading == null
icon, ? null
color: Colors.black, : Icon(
), leading,
color: Colors.black,
),
trailing: trailing
? const Icon(
Icons.keyboard_arrow_right,
color: Colors.black,
)
: null,
title: Text( title: Text(
label, label,
style: const TextStyle(fontSize: 15), style: TextStyle(fontSize: 15, color: color),
), ),
); );
} }
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class GeneralPrimaryButton extends StatelessWidget {
const GeneralPrimaryButton({super.key});
@override
Widget build(BuildContext context) {
return Container();
}
}
+2 -2
View File
@@ -23,10 +23,10 @@ class UserDI {
injector.registerSingleton<UpdateUserInfoBloc>((() => injector.registerSingleton<UpdateUserInfoBloc>((() =>
UpdateUserInfoBloc(userRepository: injector.get<UserRepository>()))); UpdateUserInfoBloc(userRepository: injector.get<UserRepository>())));
injector.registerSingleton<SignInBloc>( injector.registerDependency<SignInBloc>(
(() => SignInBloc(userRepository: injector.get<UserRepository>()))); (() => SignInBloc(userRepository: injector.get<UserRepository>())));
injector.registerSingleton<SignUpBloc>( injector.registerDependency<SignUpBloc>(
(() => SignUpBloc(userRepository: injector.get<UserRepository>()))); (() => SignUpBloc(userRepository: injector.get<UserRepository>())));
} }
} }
+26 -25
View File
@@ -103,32 +103,33 @@ class _SignInScreenState extends State<SignInScreen> {
width: MediaQuery.of(context).size.width * 0.9, width: MediaQuery.of(context).size.width * 0.9,
height: 50, height: 50,
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
context.read<SignInBloc>().add(SignInRequired( context.read<SignInBloc>().add(SignInRequired(
emailController.text, emailController.text, passwordController.text));
passwordController.text)); }
} },
}, style: TextButton.styleFrom(
style: TextButton.styleFrom( elevation: 3.0,
elevation: 3.0, backgroundColor:
backgroundColor: Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.primary, foregroundColor: Colors.white,
foregroundColor: Colors.white, shape: RoundedRectangleBorder(
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(60))),
borderRadius: BorderRadius.circular(60))), child: const Padding(
child: const Padding( padding:
padding: EdgeInsets.symmetric( EdgeInsets.symmetric(horizontal: 25, vertical: 5),
horizontal: 25, vertical: 5), child: Text(
child: Text( 'Iniciar Sesión',
'Sign In', textAlign: TextAlign.center,
textAlign: TextAlign.center, style: TextStyle(
style: TextStyle( color: Colors.white,
color: Colors.white, fontSize: 16,
fontSize: 16, fontWeight: FontWeight.w600,
fontWeight: FontWeight.w600),
), ),
)), ),
),
),
) )
: const CircularProgressIndicator() : const CircularProgressIndicator()
], ],
+131
View File
@@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injector/injector.dart';
import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:prosappco/blocs/sign_up_bloc/sign_up_bloc.dart';
import 'package:prosappco/blocs/sing_in_bloc/sign_in_bloc.dart';
import 'package:prosappco/screens/authentication/sign_in_screen.dart';
import 'package:prosappco/screens/authentication/sign_up_screen.dart';
class SignScreen extends StatefulWidget {
final int initialIndex;
SignScreen({super.key, required this.initialIndex});
@override
State<SignScreen> createState() => _SignScreenState();
}
class _SignScreenState extends State<SignScreen> with TickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(
initialIndex: widget.initialIndex,
length: 2,
vsync: this,
);
}
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
Navigator.pop(context);
}
},
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.tertiary,
),
body: Column(
children: [
// Text('${context.read<SignInBloc>().state}'),
Container(
color: Theme.of(context).colorScheme.tertiary,
child: Column(
children: [
Center(
child: Image(
width: width * 0.7,
image: AssetImage('images/logo_prosapp.png'),
),
),
SizedBox(height: 20),
TabBar(
controller: tabController,
unselectedLabelColor:
Theme.of(context).colorScheme.onBackground,
labelColor: Theme.of(context).colorScheme.onBackground,
tabs: [
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Inicia sesión',
style: TextStyle(
fontSize: 18,
),
),
),
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Registrate',
style: TextStyle(
fontSize: 18,
),
),
),
],
),
],
),
),
Expanded(
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
Expanded(
child: TabBarView(controller: tabController, children: [
BlocProvider<SignInBloc>(
create: (context) =>
Injector.appInstance.get<SignInBloc>(),
child: SignInScreen(),
),
BlocProvider<SignUpBloc>(
create: (context) =>
Injector.appInstance.get<SignUpBloc>(),
child: SignUpScreen(),
),
// BlocProvider<SignInBloc>(
// create: (context) => SignInBloc(
// userRepository: context
// .read<AuthenticationBloc>()
// .userRepository),
// child: SignInScreen(),
// ),
// BlocProvider<SignUpBloc>(
// create: (context) => SignUpBloc(
// userRepository: context
// .read<AuthenticationBloc>()
// .userRepository),
// child: SignUpScreen(),
// ),
]),
)
],
),
),
),
),
],
),
),
);
}
}
+206 -204
View File
@@ -45,212 +45,214 @@ class _SignUpScreenState extends State<SignUpScreen> {
return; return;
} }
}, },
child: Form( child: Scaffold(
key: _formKey, body: Form(
child: Center( key: _formKey,
child: Column( child: Center(
children: [ child: Column(
const SizedBox(height: 20), children: [
SizedBox( const SizedBox(height: 20),
width: MediaQuery.of(context).size.width * 0.9, SizedBox(
child: MyTextField( width: MediaQuery.of(context).size.width * 0.9,
controller: emailController, child: MyTextField(
hintText: 'Email', controller: emailController,
obscureText: false, hintText: 'Email',
keyboardType: TextInputType.emailAddress, obscureText: false,
prefixIcon: const Icon(CupertinoIcons.mail_solid), keyboardType: TextInputType.emailAddress,
validator: (val) { prefixIcon: const Icon(CupertinoIcons.mail_solid),
if (val!.isEmpty) { validator: (val) {
return 'Please fill in this field'; if (val!.isEmpty) {
} else if (!emailRexExp.hasMatch(val)) { return 'Please fill in this field';
return 'Please enter a valid email'; } else if (!emailRexExp.hasMatch(val)) {
} return 'Please enter a valid email';
return null; }
}), return null;
), }),
const SizedBox(height: 10), ),
SizedBox( const SizedBox(height: 10),
width: MediaQuery.of(context).size.width * 0.9, SizedBox(
child: MyTextField( width: MediaQuery.of(context).size.width * 0.9,
controller: passwordController, child: MyTextField(
hintText: 'Password', controller: passwordController,
obscureText: obscurePassword, hintText: 'Password',
keyboardType: TextInputType.visiblePassword, obscureText: obscurePassword,
prefixIcon: const Icon(CupertinoIcons.lock_fill), keyboardType: TextInputType.visiblePassword,
onChanged: (val) { prefixIcon: const Icon(CupertinoIcons.lock_fill),
if (val!.contains(RegExp(r'[A-Z]'))) { onChanged: (val) {
setState(() { if (val!.contains(RegExp(r'[A-Z]'))) {
containsUpperCase = true; setState(() {
}); containsUpperCase = true;
} else { });
setState(() { } else {
containsUpperCase = false; setState(() {
}); containsUpperCase = false;
} });
if (val.contains(RegExp(r'[a-z]'))) { }
setState(() { if (val.contains(RegExp(r'[a-z]'))) {
containsLowerCase = true; setState(() {
}); containsLowerCase = true;
} else { });
setState(() { } else {
containsLowerCase = false; setState(() {
}); containsLowerCase = false;
} });
if (val.contains(RegExp(r'[0-9]'))) { }
setState(() { if (val.contains(RegExp(r'[0-9]'))) {
containsNumber = true; setState(() {
}); containsNumber = true;
} else { });
setState(() { } else {
containsNumber = false; setState(() {
}); containsNumber = false;
} });
if (val.contains(specialCharRexExp)) { }
setState(() { if (val.contains(specialCharRexExp)) {
containsSpecialChar = true; setState(() {
}); containsSpecialChar = true;
} else { });
setState(() { } else {
containsSpecialChar = false; setState(() {
}); containsSpecialChar = false;
} });
if (val.length >= 8) { }
setState(() { if (val.length >= 8) {
contains8Length = true; setState(() {
}); contains8Length = true;
} else { });
setState(() { } else {
contains8Length = false; setState(() {
}); contains8Length = false;
} });
return null; }
}, return null;
suffixIcon: IconButton(
onPressed: () {
setState(() {
obscurePassword = !obscurePassword;
if (obscurePassword) {
iconPassword = CupertinoIcons.eye_fill;
} else {
iconPassword = CupertinoIcons.eye_slash_fill;
}
});
}, },
icon: Icon(iconPassword), suffixIcon: IconButton(
), onPressed: () {
validator: (val) { setState(() {
if (val!.isEmpty) { obscurePassword = !obscurePassword;
return 'Please fill in this field'; if (obscurePassword) {
} else if (!passwordRexExp.hasMatch(val)) { iconPassword = CupertinoIcons.eye_fill;
return 'Please enter a valid password'; } else {
} iconPassword = CupertinoIcons.eye_slash_fill;
return null;
}),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"⚈ 1 uppercase",
style: TextStyle(
color: containsUpperCase
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
Text(
"⚈ 1 lowercase",
style: TextStyle(
color: containsLowerCase
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"⚈ 8 minimum character",
style: TextStyle(
color: contains8Length
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
Text(
"⚈ 1 number",
style: TextStyle(
color: containsNumber
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
],
),
],
),
const SizedBox(height: 10),
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: MyTextField(
controller: nameController,
hintText: 'Name',
obscureText: false,
keyboardType: TextInputType.name,
prefixIcon: const Icon(CupertinoIcons.person_fill),
validator: (val) {
if (val!.isEmpty) {
return 'Please fill in this field';
} else if (val.length > 30) {
return 'Name too long';
}
return null;
}),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
!signUpRequired
? SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
MyUser myUser = MyUser.empty;
myUser = myUser.copyWith(
email: emailController.text,
name: nameController.text,
);
setState(() {
context.read<SignUpBloc>().add(SignUpRequired(
myUser, passwordController.text));
});
} }
}, });
style: TextButton.styleFrom( },
elevation: 3.0, icon: Icon(iconPassword),
backgroundColor: ),
Theme.of(context).colorScheme.primary, validator: (val) {
foregroundColor: Colors.white, if (val!.isEmpty) {
shape: RoundedRectangleBorder( return 'Please fill in this field';
borderRadius: BorderRadius.circular(60))), } else if (!passwordRexExp.hasMatch(val)) {
child: const Padding( return 'Please enter a valid password';
padding: EdgeInsets.symmetric( }
horizontal: 25, vertical: 5), return null;
child: Text( }),
'Sign Up', ),
textAlign: TextAlign.center, const SizedBox(height: 10),
style: TextStyle( Row(
color: Colors.white, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
fontSize: 16, crossAxisAlignment: CrossAxisAlignment.start,
fontWeight: FontWeight.w600), children: [
), Column(
)), crossAxisAlignment: CrossAxisAlignment.start,
) children: [
: const CircularProgressIndicator() Text(
], "⚈ 1 uppercase",
style: TextStyle(
color: containsUpperCase
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
Text(
"⚈ 1 lowercase",
style: TextStyle(
color: containsLowerCase
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"⚈ 8 minimum character",
style: TextStyle(
color: contains8Length
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
Text(
"⚈ 1 number",
style: TextStyle(
color: containsNumber
? Colors.green
: Theme.of(context).colorScheme.onBackground),
),
],
),
],
),
const SizedBox(height: 10),
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: MyTextField(
controller: nameController,
hintText: 'Name',
obscureText: false,
keyboardType: TextInputType.name,
prefixIcon: const Icon(CupertinoIcons.person_fill),
validator: (val) {
if (val!.isEmpty) {
return 'Please fill in this field';
} else if (val.length > 30) {
return 'Name too long';
}
return null;
}),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
!signUpRequired
? SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
MyUser myUser = MyUser.empty;
myUser = myUser.copyWith(
email: emailController.text,
name: nameController.text,
);
setState(() {
context.read<SignUpBloc>().add(SignUpRequired(
myUser, passwordController.text));
});
}
},
style: TextButton.styleFrom(
elevation: 3.0,
backgroundColor:
Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(60))),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 25, vertical: 5),
child: Text(
'Sign Up',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600),
),
)),
)
: const CircularProgressIndicator(),
],
),
), ),
), ),
), ),
+112 -86
View File
@@ -1,102 +1,128 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/services.dart';
import 'package:prosappco/blocs/sing_in_bloc/sign_in_bloc.dart'; import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:prosappco/screens/authentication/sign_in_screen.dart'; import 'package:prosappco/screens/authentication/sign_screen.dart';
import 'package:prosappco/screens/authentication/sign_up_screen.dart';
import '../../blocs/authentication_bloc/authentication_bloc.dart'; class WelcomeScreen extends StatelessWidget {
import '../../blocs/sign_up_bloc/sign_up_bloc.dart';
class WelcomeScreen extends StatefulWidget {
const WelcomeScreen({super.key}); const WelcomeScreen({super.key});
@override
State<WelcomeScreen> createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen>
with TickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.surface,
appBar: AppBar( body: Column(
elevation: 0, children: [
backgroundColor: Colors.transparent, Container(
), color: Theme.of(context).colorScheme.tertiary,
body: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
children: [ children: [
const Text( const SizedBox(height: 20),
'Welcome Back !', Center(
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), child: Image(
width: width * 0.7,
image: const AssetImage('images/logo_prosapp.png'),
),
), ),
const SizedBox(height: kToolbarHeight), const SizedBox(height: 20),
TabBar(
controller: tabController,
unselectedLabelColor: Theme.of(context)
.colorScheme
.onBackground
.withOpacity(0.5),
labelColor: Theme.of(context).colorScheme.onBackground,
tabs: const [
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Sign In',
style: TextStyle(
fontSize: 18,
),
),
),
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Sign Up',
style: TextStyle(
fontSize: 18,
),
),
),
]),
Expanded(
child: TabBarView(controller: tabController, children: [
BlocProvider<SignInBloc>(
create: (context) => SignInBloc(
userRepository: context
.read<AuthenticationBloc>()
.userRepository),
child: const SignInScreen(),
),
BlocProvider<SignUpBloc>(
create: (context) => SignUpBloc(
userRepository: context
.read<AuthenticationBloc>()
.userRepository),
child: const SignUpScreen(),
),
]),
)
], ],
), ),
), ),
), SizedBox(
width: double.infinity,
child: Text(
'Iniciar sesión',
style: TextStyle(
// fontSize: 30,
fontSize: width * 0.08,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: Text(
'Numero de celular',
style: TextStyle(
fontSize: width * 0.045,
color: Theme.of(context).colorScheme.onBackground,
),
),
),
Form(
child: IntlPhoneField(
initialCountryCode: 'CO',
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
),
),
Text(
'Un código será enviado a este numero de celular.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.0,
color: Theme.of(context).colorScheme.onBackground,
),
),
TextButton(
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => SignScreen(initialIndex: 0),
),
);
},
child: Text(
'Inicia sesión con tu correo electrónico',
style: TextStyle(
fontSize: width * 0.04,
),
),
),
RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 16.0,
color: Color(0xFF65676B),
fontFamily: 'Poppins',
),
children: [
const TextSpan(text: '¿No estás registrado? '),
TextSpan(
text: 'Regístrate',
style: TextStyle(
fontSize: width * 0.04,
color: Colors.blue,
fontWeight: FontWeight.w600,
),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) =>
SignScreen(initialIndex: 1),
),
);
},
),
],
),
),
],
),
),
),
],
), ),
); );
} }
@@ -0,0 +1,171 @@
// import 'package:cloud_firestore/cloud_firestore.dart';
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:prosappco/src/components/pop_appbar.dart';
// import 'package:prosappco/src/presentation/screens/about.dart';
// class ConfigurationScreen extends StatefulWidget {
// const ConfigurationScreen({super.key});
// @override
// State<ConfigurationScreen> createState() => _ConfigurationScreenState();
// }
// class _ConfigurationScreenState extends State<ConfigurationScreen> {
// late final FirebaseAuth _auth;
// @override
// void initState() {
// super.initState();
// _auth = FirebaseAuth.instance;
// }
// Future<void> deleteAccount() async {
// try {
// final currentUser = _auth.currentUser;
// if (currentUser != null) {
// final uid = currentUser.uid;
// await FirebaseFirestore.instance.collection('users').doc(uid).delete();
// await currentUser.delete();
// await _auth.signOut();
// Get.snackbar(
// 'Cuenta Eliminada',
// 'Tu cuenta ha sido eliminada con éxito.',
// snackPosition: SnackPosition.BOTTOM,
// );
// }
// } catch (e) {
// Get.snackbar(
// 'Error al Eliminar Cuenta',
// 'Hubo un error al eliminar tu cuenta. Por favor, inténtalo de nuevo más tarde.',
// snackPosition: SnackPosition.BOTTOM,
// );
// }
// }
// Future<void> _showDeleteAccountConfirmationDialog(
// BuildContext context) async {
// return showDialog(
// context: context,
// builder: (BuildContext context) {
// return AlertDialog(
// title: const Text('Eliminar Cuenta'),
// content: const Text(
// '¿Estás seguro de que deseas eliminar tu cuenta? Esta acción no se puede deshacer.'),
// actions: [
// TextButton(
// onPressed: () {
// Navigator.of(context).pop();
// },
// child: const Text('Cancelar'),
// ),
// TextButton(
// onPressed: () {
// deleteAccount();
// Navigator.of(context).pop();
// },
// child: const Text(
// 'Eliminar',
// style:
// TextStyle(color: Colors.red, fontWeight: FontWeight.w600),
// ),
// ),
// ],
// );
// },
// );
// }
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: PopAppbar(
// onPressed: () {
// Navigator.pop(context);
// },
// label: 'Configuración'),
// body: ListView(
// children: [
// ListTile(
// onTap: () {
// Navigator.push(
// context,
// CupertinoPageRoute(
// builder: (BuildContext context) {
// return const AboutScreen();
// },
// ),
// );
// },
// title: const Text('Acerca de la aplicación'),
// trailing: const Icon(
// Icons.keyboard_arrow_right,
// color: Colors.black,
// ),
// ),
// ListTile(
// onTap: () {
// _showDeleteAccountConfirmationDialog(context);
// },
// title: const Text(
// 'Eliminar cuenta',
// style: TextStyle(color: Colors.red),
// ),
// ),
// ],
// ),
// );
// }
// }
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:prosappco/components/general_drawer_item.dart';
class ConfigurationScreen extends StatelessWidget {
const ConfigurationScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state.status == AuthenticationStatus.unauthenticated) {
Navigator.pop(context);
}
},
child: Scaffold(
appBar: AppBar(
title: const Text('Configuración'),
),
body: ListView(children: [
GeneralDrawerItem(
label: 'Acerca de la aplicación',
onTap: () {},
trailing: true,
),
GeneralDrawerItem(
label: 'Cerrar sesión',
onTap: () {
context
.read<AuthenticationBloc>()
.add(AuthenticationLogoutRequested());
},
),
GeneralDrawerItem(
label: 'Eliminar cuenta',
onTap: () {},
color: Theme.of(context).colorScheme.error,
)
]),
),
);
}
}
+8 -26
View File
@@ -1,36 +1,18 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart';
import 'package:prosappco/blocs/update_user_info_bloc/update_user_info_bloc.dart';
import 'package:prosappco/components/general_drawer.dart'; import 'package:prosappco/components/general_drawer.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatelessWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>( return SafeArea(
listener: (context, state) { child: Scaffold(
if (state is UploadPictureSuccess) { backgroundColor: Theme.of(context).colorScheme.background,
setState(() { drawer: GeneralDrawer(),
context.read<MyUserBloc>().state.user!.picture = state.userImage; appBar: AppBar(),
}); body: const Center(
} child: Text('Bienvenido'),
},
child: SafeArea(
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
drawer: const GeneralDrawer(),
appBar: AppBar(),
body: const Center(
child: Text('Bienvenido'),
),
), ),
), ),
); );
+1 -3
View File
@@ -27,9 +27,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>( return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>(
listener: (context, state) { listener: (context, state) {
if (state is UploadPictureSuccess) { if (state is UploadPictureSuccess) {
setState(() { setState(() {});
context.read<MyUserBloc>().state.user!.picture = state.userImage;
});
} }
}, },
child: Scaffold( child: Scaffold(
+25 -24
View File
@@ -69,6 +69,30 @@ class _LoginScreenState extends State<LoginScreen> {
} }
Widget _mobileView(BuildContext context) { Widget _mobileView(BuildContext context) {
const inputDecoration = InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
);
return BottomSheetExpanded( return BottomSheetExpanded(
children: [ children: [
const SizedBox( const SizedBox(
@@ -103,30 +127,7 @@ class _LoginScreenState extends State<LoginScreen> {
onChanged: (phoneNo) { onChanged: (phoneNo) {
completePhoneNumber = phoneNo.completeNumber; completePhoneNumber = phoneNo.completeNumber;
}, },
decoration: const InputDecoration( decoration: inputDecoration,
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color.fromARGB(255, 184, 0, 0)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFECECEC)),
borderRadius: BorderRadius.all(
Radius.circular(50),
)),
fillColor: Color.fromARGB(255, 239, 239, 239),
filled: true,
),
), ),
), ),
const Text( const Text(
@@ -0,0 +1,36 @@
import 'package:equatable/equatable.dart';
class CityEntity extends Equatable {
final String name;
final String coords;
const CityEntity({
required this.name,
required this.coords,
});
Map<String, Object?> toDocument() {
return {
'name': name,
'coords': coords,
};
}
static CityEntity fromDocument(Map<String, dynamic> doc) {
return CityEntity(
name: doc['name'] as String,
coords: doc['coords'] as String,
);
}
@override
List<Object?> get props => [name, coords];
@override
String toString() {
return '''CityEntity {
name: $name
coords: $coords
}''';
}
}
@@ -0,0 +1,39 @@
import 'package:equatable/equatable.dart';
import 'package:user_repository/user_repository.dart';
class CountryEntity extends Equatable {
final String name;
final List<RegionEntity> regions;
const CountryEntity({
required this.name,
required this.regions,
});
Map<String, Object?> toDocument() {
return {
'name': name,
'regions': regions,
};
}
static CountryEntity fromDocument(Map<String, dynamic> doc) {
return CountryEntity(
name: doc['name'] as String,
regions: (doc['regions'] as List)
.map((region) => RegionEntity.fromDocument(region))
.toList(),
);
}
@override
List<Object?> get props => [name, regions];
@override
String toString() {
return '''CountryEntity {
name: $name
regions: $regions
}''';
}
}
@@ -1 +1,4 @@
export 'my_user_entity.dart'; export 'my_user_entity.dart';
export 'country_entity.dart';
export 'region_entity.dart';
export 'city_entity.dart';
@@ -0,0 +1,39 @@
import 'package:equatable/equatable.dart';
import 'package:user_repository/user_repository.dart';
class RegionEntity extends Equatable {
final String name;
final List<CityEntity> cities;
const RegionEntity({
required this.name,
required this.cities,
});
Map<String, Object?> toDocument() {
return {
'name': name,
'cities': cities,
};
}
static RegionEntity fromDocument(Map<String, dynamic> doc) {
return RegionEntity(
name: doc['name'] as String,
cities: (doc['cities'] as List)
.map((city) => CityEntity.fromDocument(city))
.toList(),
);
}
@override
List<Object?> get props => [name, cities];
@override
String toString() {
return '''RegionEntity {
name: $name
cities: $cities
}''';
}
}
@@ -0,0 +1,46 @@
import 'package:equatable/equatable.dart';
import '../entities/entities.dart';
class City extends Equatable {
final String name;
final String coords;
const City({
required this.name,
required this.coords,
});
static const empty = City(name: '', coords: '');
City copyWith({
String? name,
String? coords,
}) {
return City(
name: name ?? this.name,
coords: coords ?? this.coords,
);
}
bool get isEmpty => this == City.empty;
bool get isNotEmpty => this != City.empty;
City toEntity() {
return City(
name: name,
coords: coords,
);
}
static City fromEntity(CityEntity entity) {
return City(
name: entity.name,
coords: entity.coords,
);
}
@override
List<Object?> get props => [name, coords];
}
@@ -0,0 +1,42 @@
import 'package:equatable/equatable.dart';
import 'package:user_repository/user_repository.dart';
class Country extends Equatable {
final String name;
final List<Region> regions;
const Country({
required this.name,
required this.regions,
});
static const empty = Country(name: '', regions: []);
Country copyWith({
String? name,
List<Region>? regions,
}) {
return Country(
name: name ?? this.name,
regions: regions ?? this.regions,
);
}
bool get isEmpty => this == Country.empty;
bool get isNotEmpty => this != Country.empty;
Country toEntity() {
return Country(name: name, regions: regions);
}
static Country fromEntity(CountryEntity entity) {
return Country(
name: entity.name,
regions:
entity.regions.map((region) => Region.fromEntity(region)).toList(),
);
}
@override
List<Object?> get props => [name, regions];
}
@@ -1 +1,4 @@
export 'my_user.dart'; export 'my_user.dart';
export 'country.dart';
export 'region.dart';
export 'city.dart';
@@ -60,11 +60,3 @@ class MyUser extends Equatable {
@override @override
List<Object?> get props => [id, email, name, picture]; List<Object?> get props => [id, email, name, picture];
} }
// final String name;
// final String city;
// final String? profession;
// final String? state;
// final Reference? photo;
// final int? tarifa;
// final String? phoneNumber;
// final String? token;
@@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
import 'package:user_repository/user_repository.dart';
class Region extends Equatable {
final String name;
final List<City> cities;
const Region({
required this.name,
required this.cities,
});
static const empty = Region(name: '', cities: []);
Region copyWith({
String? name,
List<City>? cities,
}) {
return Region(
name: name ?? this.name,
cities: cities ?? this.cities,
);
}
bool get isEmpty => this == Region.empty;
bool get isNotEmpty => this != Region.empty;
Region toEntity() {
return Region(name: name, cities: cities);
}
static Region fromEntity(RegionEntity entity) {
return Region(
name: entity.name,
cities: entity.cities.map((city) => City.fromEntity(city)).toList(),
);
}
@override
List<Object?> get props => [name, cities];
}
@@ -0,0 +1 @@
abstract class CityRepository {}
@@ -0,0 +1,7 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'city_repo.dart';
class FirebaseCityRepository implements CityRepository {
final usersCollection = FirebaseFirestore.instance.collection('users');
}
@@ -6,7 +6,7 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage/firebase_storage.dart';
import 'package:user_repository/src/models/my_user.dart'; import 'package:user_repository/src/models/my_user.dart';
import 'entities/entities.dart'; import '../entities/entities.dart';
import 'user_repo.dart'; import 'user_repo.dart';
class FirebaseUserRepository implements UserRepository { class FirebaseUserRepository implements UserRepository {
@@ -42,14 +42,6 @@ class FirebaseUserRepository implements UserRepository {
return _userStreamController.stream; return _userStreamController.stream;
} }
// @override
// Stream<User?> get user {
// return _firebaseAuth.authStateChanges().map((firebaseUser) {
// final user = firebaseUser;
// return user;
// });
// }
// Sign up // Sign up
@override @override
Future<MyUser> signUp(MyUser myUser, String password) async { Future<MyUser> signUp(MyUser myUser, String password) async {
@@ -70,6 +62,46 @@ class FirebaseUserRepository implements UserRepository {
} }
} }
// Sign in with phone number
@override
Future<void> signInWithPhoneNumber(String phoneNumber) async {
try {
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (PhoneAuthCredential credential) async {
// Esta función se llama automáticamente cuando se completa la verificación del número de teléfono.
// Puedes usar 'credential' para iniciar sesión o vincular la cuenta.
// En la mayoría de los casos, no necesitas implementar esto, ya que Firebase manejará la autenticación automáticamente.
await FirebaseAuth.instance.signInWithCredential(credential);
},
verificationFailed: (FirebaseAuthException e) {
// Esta función se llama si la verificación del número de teléfono falla.
// Maneja los errores o muestra un mensaje al usuario.
if (e.code == 'invalid-phone-number') {
// Manejar el caso de número de teléfono no válido
} else if (e.code == 'network-request-failed') {
// Manejar problemas de conectividad
} else {
// Manejar otros errores
}
},
codeSent: (String verificationId, int? resendToken) {
// Esta función se llama cuando se envía el código de verificación al número de teléfono del usuario.
// Debes guardar 'verificationId' para usarlo posteriormente en la verificación.
// Puedes mostrar un diálogo para que el usuario ingrese el código o puedes verificarlo automáticamente.
},
codeAutoRetrievalTimeout: (String verificationId) {
// Esta función se llama cuando el tiempo de espera de recuperación automática del código ha expirado.
// Puedes manejar esto como prefieras, por ejemplo, mostrando un mensaje al usuario o reenviando el código.
},
);
} catch (e) {
log(e.toString());
rethrow;
}
}
// Sign in // Sign in
@override @override
Future<void> signIn(String email, String password) async { Future<void> signIn(String email, String password) async {
@@ -1,4 +1,4 @@
import '../user_repository.dart'; import '../../user_repository.dart';
abstract class UserRepository { abstract class UserRepository {
// Stream<User?> get user; // Stream<User?> get user;
@@ -10,6 +10,8 @@ abstract class UserRepository {
Future<MyUser> signUp(MyUser myUser, String password); Future<MyUser> signUp(MyUser myUser, String password);
Future<void> signInWithPhoneNumber(String phoneNumber);
Future<void> resetPassword(String email); Future<void> resetPassword(String email);
Future<void> setUserData(MyUser user); Future<void> setUserData(MyUser user);
@@ -2,5 +2,5 @@ library user_repository;
export 'src/models/models.dart'; export 'src/models/models.dart';
export 'src/entities/entities.dart'; export 'src/entities/entities.dart';
export 'src/user_repo.dart'; export 'src/repositories/user_repo.dart';
export 'src/firebase_user_repository.dart'; export 'src/repositories/firebase_user_repository.dart';
+8
View File
@@ -853,6 +853,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
otp_timer_button:
dependency: "direct main"
description:
name: otp_timer_button
sha256: e6516573bc31b99ae3b67f4f8f8bbe721672f6f4d04c96e493b08b5ff1afa06e
url: "https://pub.dev"
source: hosted
version: "1.1.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
+5 -4
View File
@@ -28,9 +28,10 @@ dependencies:
flutter_local_notifications: ^14.1.1 flutter_local_notifications: ^14.1.1
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_otp_text_field: null
flutter_polyline_points: ^2.0.0 flutter_polyline_points: ^2.0.0
flutter_rating_bar: null flutter_otp_text_field: ^1.1.1
flutter_rating_bar: ^4.0.1
otp_timer_button: ^1.1.0
font_awesome_flutter: ^10.4.0 font_awesome_flutter: ^10.4.0
geocoding: ^2.1.0 geocoding: ^2.1.0
geolocator: ^9.0.2 geolocator: ^9.0.2
@@ -40,8 +41,8 @@ dependencies:
http: ^1.1.0 http: ^1.1.0
image_picker: ^1.0.7 image_picker: ^1.0.7
injector: ^3.0.0 injector: ^3.0.0
intl: any intl_phone_field: ^3.2.0
intl_phone_field: null intl: ^0.18.1
location: ^5.0.3 location: ^5.0.3
package_info_plus: ^4.2.0 package_info_plus: ^4.2.0
provider: ^6.0.5 provider: ^6.0.5