diff --git a/lib/app.dart b/lib/app.dart index b20119f..b493553 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -25,16 +25,16 @@ class MainApp extends StatelessWidget { BlocProvider( create: (context) => Injector.appInstance.get(), ), - BlocProvider( - create: (context) => Injector.appInstance.get(), - ), - BlocProvider( - create: (context) => Injector.appInstance.get(), - ) + // BlocProvider( + // create: (context) => Injector.appInstance.get(), + // ), + // BlocProvider( + // create: (context) => Injector.appInstance.get(), + // ), ], child: BlocBuilder( builder: (context, state) { - return const MyAppView(); + return const SafeArea(child: MyAppView()); }, ), ); diff --git a/lib/app_view.dart b/lib/app_view.dart index fd0f1be..362d23d 100644 --- a/lib/app_view.dart +++ b/lib/app_view.dart @@ -16,29 +16,34 @@ class MyAppView extends StatelessWidget { title: 'Prosappco', theme: ThemeData( colorScheme: const ColorScheme.light( - background: Colors.white, - onBackground: Colors.black, - primary: Color.fromRGBO(66, 164, 239, 1), - onPrimary: Colors.black, - secondary: Color.fromRGBO(35, 108, 244, 1), - onSecondary: Colors.white, - tertiary: Color.fromRGBO(255, 204, 128, 1), - error: Colors.red, - outline: Color(0xFF424242)), + background: Colors.white, + onBackground: Colors.black, + primary: Color.fromRGBO(66, 164, 239, 1), + onPrimary: Colors.black, + secondary: Color.fromRGBO(35, 108, 244, 1), + onSecondary: Colors.white, + tertiary: Color.fromRGBO(214, 244, 255, 1), + error: Colors.red, + outline: Color(0xFF424242), + ), ), home: BlocBuilder( builder: (context, state) { - switch (state.status) { - case AuthenticationStatus.authenticated: - return const HomeScreen(); - - case AuthenticationStatus.unauthenticated: - return const WelcomeScreen(); - - case AuthenticationStatus.unknown: - return const SplashScreen(); - } + return getScreen(state); }), ); } + + getScreen(AuthenticationState state) { + switch (state.status) { + case AuthenticationStatus.authenticated: + return const HomeScreen(); + + case AuthenticationStatus.unauthenticated: + return WelcomeScreen(); + + case AuthenticationStatus.unknown: + return const SplashScreen(); + } + } } diff --git a/lib/blocs/authentication_bloc/authentication_bloc.dart b/lib/blocs/authentication_bloc/authentication_bloc.dart index 470fce1..f63a213 100644 --- a/lib/blocs/authentication_bloc/authentication_bloc.dart +++ b/lib/blocs/authentication_bloc/authentication_bloc.dart @@ -19,13 +19,23 @@ class AuthenticationBloc _userSubscription = userRepository.streamUser().listen((authUser) { add(AuthenticationUserChanged(authUser)); }); - on((event, emit) { - if (event.user != null) { - emit(AuthenticationState.authenticated(event.user!)); - } else { - emit(const AuthenticationState.unauthenticated()); - } - }); + on(_onAuthenticationUserChanged); + on(_onAuthenticationLogoutRequested); + } + + void _onAuthenticationUserChanged( + AuthenticationUserChanged event, Emitter emit) { + emit( + event.user != null + ? AuthenticationState.authenticated(event.user!) + : const AuthenticationState.unauthenticated(), + ); + } + + void _onAuthenticationLogoutRequested(AuthenticationLogoutRequested event, + Emitter emit) async { + await userRepository.logOut(); + emit(const AuthenticationState.unauthenticated()); } @override diff --git a/lib/blocs/sign_up_bloc/sign_up_bloc.dart b/lib/blocs/sign_up_bloc/sign_up_bloc.dart index 36d0fef..f2e9b1e 100644 --- a/lib/blocs/sign_up_bloc/sign_up_bloc.dart +++ b/lib/blocs/sign_up_bloc/sign_up_bloc.dart @@ -11,15 +11,18 @@ class SignUpBloc extends Bloc { SignUpBloc({required UserRepository userRepository}) : _userRepository = userRepository, super(SignUpInitial()) { - on((event, emit) async { - emit(SignUpProcess()); - try { - MyUser user = await _userRepository.signUp(event.user, event.password); - await _userRepository.setUserData(user); - emit(SignUpSuccess()); - } catch (e) { - emit(SignUpFailure()); - } - }); + on(_onSignUpRequired); + } + + void _onSignUpRequired( + SignUpRequired event, Emitter emit) async { + emit(SignUpProcess()); + try { + MyUser user = await _userRepository.signUp(event.user, event.password); + await _userRepository.setUserData(user); + emit(SignUpSuccess()); + } catch (e) { + emit(SignUpFailure()); + } } } diff --git a/lib/blocs/sing_in_bloc/sign_in_bloc.dart b/lib/blocs/sing_in_bloc/sign_in_bloc.dart index 7c2c33a..648b3ae 100644 --- a/lib/blocs/sing_in_bloc/sign_in_bloc.dart +++ b/lib/blocs/sing_in_bloc/sign_in_bloc.dart @@ -14,18 +14,35 @@ class SignInBloc extends Bloc { SignInBloc({required UserRepository userRepository}) : _userRepository = userRepository, super(SignInInitial()) { - on((event, emit) async { - emit(SignInProcess()); + userRepository.streamUser().listen((authUser) { try { - await _userRepository.signIn(event.email, event.password); - emit(SignInSuccess()); + if (authUser == null) { + emit(SignInFailure()); + } else { + emit(SignInSuccess()); + } } catch (e) { log(e.toString()); - emit(const SignInFailure()); } }); - on((event, emit) async { - await _userRepository.logOut(); - }); + on(_onSignInRequired); + on(_onSignOutRequired); + } + + void _onSignInRequired( + SignInRequired event, Emitter 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 emit) async { + await _userRepository.logOut(); } } diff --git a/lib/components/general_drawer.dart b/lib/components/general_drawer.dart index a56a6ff..dcf633b 100644 --- a/lib/components/general_drawer.dart +++ b/lib/components/general_drawer.dart @@ -1,96 +1,91 @@ import 'package:flutter/cupertino.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_item.dart'; +import 'package:prosappco/screens/configuration/configuration_screen.dart'; -class GeneralDrawer extends StatefulWidget { +class GeneralDrawer extends StatelessWidget { const GeneralDrawer({super.key}); - @override - State createState() => _GeneralDrawerState(); -} - -class _GeneralDrawerState extends State { @override Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state is UploadPictureSuccess) { - setState(() { - context.read().state.user!.picture = state.userImage; - }); - } - }, - child: Drawer( - backgroundColor: Theme.of(context).colorScheme.background, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const GeneralDrawerHeader(), - Divider( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1), - thickness: 0.5, - height: 1, - ), - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - GeneralDrawerItem( - icon: Icons.person_outline, - label: 'Mi Perfil', - onTap: () { - Navigator.pop(context); - }, - ), - ], - ), + return Drawer( + backgroundColor: Theme.of(context).colorScheme.background, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const GeneralDrawerHeader(), + Divider( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1), + thickness: 0.5, + height: 1, + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + GeneralDrawerItem( + leading: Icons.history, + label: 'Mis servicios', + onTap: () { + Navigator.pop(context); + }, + ), + GeneralDrawerItem( + leading: Icons.settings_outlined, + label: 'Configuración', + onTap: () { + 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), - thickness: 0.5, - height: 1, - ), - const SizedBox(height: 15), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.primary, - padding: const EdgeInsets.symmetric(vertical: 15), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - child: const Text( - 'Modo Profesional', - style: TextStyle(color: Colors.white, fontSize: 18), - ), + ), + Divider( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.1), + thickness: 0.5, + height: 1, + ), + const SizedBox(height: 15), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + child: const Text( + 'Modo Profesional', + style: TextStyle(color: Colors.white, fontSize: 18), ), ), - const SizedBox(height: 15), - // Container( - // height: MediaQuery.of(context).size.height, - // color: Theme.of(context).colorScheme.background, - // child: ListView( - // children: [ - // ListTile( - // onTap: () { - // context.read().add(const SignOutRequired()); - // }, - // title: const Text( - // 'Cerrar Sesión', - // ), - // ) - // ], - // ), - // ) - ], - ), + ), + const SizedBox(height: 15), + ], ), ); } diff --git a/lib/components/general_drawer_header.dart b/lib/components/general_drawer_header.dart index b4c7d60..7c03211 100644 --- a/lib/components/general_drawer_header.dart +++ b/lib/components/general_drawer_header.dart @@ -2,78 +2,63 @@ import 'package:flutter/cupertino.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/screens/profile/profile_screen.dart'; -class GeneralDrawerHeader extends StatefulWidget { +class GeneralDrawerHeader extends StatelessWidget { const GeneralDrawerHeader({super.key}); - @override - State createState() => _GeneralDrawerHeaderState(); -} - -class _GeneralDrawerHeaderState extends State { @override Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state is UploadPictureSuccess) { - setState(() { - context.read().state.user!.picture = state.userImage; - }); - } + return ListTile( + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) { + return const ProfileScreen(); + }, + ), + ); }, - child: ListTile( - onTap: () { - Navigator.push( - context, - CupertinoPageRoute( - builder: (BuildContext context) { - return const ProfileScreen(); - }, - ), - ); - }, - title: Text( - context.read().state.user!.name, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text( - context.read().state.user!.email, - style: const TextStyle(fontSize: 12), - ), - leading: context.read().state.user!.picture == "" - ? Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, - ), - child: Icon( - CupertinoIcons.person, - color: Colors.grey.shade400, - size: 35, - ), - ) - : Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Colors.grey, - shape: BoxShape.circle, - image: DecorationImage( - image: NetworkImage( - context.read().state.user!.picture!, - ), - fit: BoxFit.cover, + title: Text( + context.read().state.user!.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text( + context.read().state.user!.email, + style: const TextStyle(fontSize: 12), + ), + leading: context.read().state.user!.picture == "" + ? Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), + child: Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 35, + ), + ) + : Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey, + shape: BoxShape.circle, + image: DecorationImage( + image: NetworkImage( + context.read().state.user!.picture!, ), + fit: BoxFit.cover, ), ), - trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black), - contentPadding: - const EdgeInsets.symmetric(vertical: 10, horizontal: 15), - ), + ), + trailing: const Icon(Icons.keyboard_arrow_right, color: Colors.black), + contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), ); } } diff --git a/lib/components/general_drawer_item.dart b/lib/components/general_drawer_item.dart index b4b8ec1..81c616b 100644 --- a/lib/components/general_drawer_item.dart +++ b/lib/components/general_drawer_item.dart @@ -2,13 +2,17 @@ import 'package:flutter/material.dart'; class GeneralDrawerItem extends StatelessWidget { final String label; - final IconData icon; + final IconData? leading; + final bool trailing; + final Color? color; final VoidCallback onTap; const GeneralDrawerItem({ super.key, required this.label, - required this.icon, + this.leading, + this.trailing = false, + this.color, required this.onTap, }); @@ -16,13 +20,21 @@ class GeneralDrawerItem extends StatelessWidget { Widget build(BuildContext context) { return ListTile( onTap: onTap, - leading: Icon( - icon, - color: Colors.black, - ), + leading: leading == null + ? null + : Icon( + leading, + color: Colors.black, + ), + trailing: trailing + ? const Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + ) + : null, title: Text( label, - style: const TextStyle(fontSize: 15), + style: TextStyle(fontSize: 15, color: color), ), ); } diff --git a/lib/components/general_primary_button.dart b/lib/components/general_primary_button.dart new file mode 100644 index 0000000..e7f8ab4 --- /dev/null +++ b/lib/components/general_primary_button.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class GeneralPrimaryButton extends StatelessWidget { + const GeneralPrimaryButton({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/dependency_user/user_di.dart b/lib/dependency_user/user_di.dart index 5ef991f..e1d2cef 100644 --- a/lib/dependency_user/user_di.dart +++ b/lib/dependency_user/user_di.dart @@ -23,10 +23,10 @@ class UserDI { injector.registerSingleton((() => UpdateUserInfoBloc(userRepository: injector.get()))); - injector.registerSingleton( + injector.registerDependency( (() => SignInBloc(userRepository: injector.get()))); - injector.registerSingleton( + injector.registerDependency( (() => SignUpBloc(userRepository: injector.get()))); } } diff --git a/lib/screens/authentication/sign_in_screen.dart b/lib/screens/authentication/sign_in_screen.dart index eaa2bc8..178f079 100644 --- a/lib/screens/authentication/sign_in_screen.dart +++ b/lib/screens/authentication/sign_in_screen.dart @@ -103,32 +103,33 @@ class _SignInScreenState extends State { width: MediaQuery.of(context).size.width * 0.9, height: 50, child: TextButton( - onPressed: () { - if (_formKey.currentState!.validate()) { - context.read().add(SignInRequired( - emailController.text, - 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 In', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w600), + onPressed: () { + if (_formKey.currentState!.validate()) { + context.read().add(SignInRequired( + emailController.text, 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( + 'Iniciar Sesión', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, ), - )), + ), + ), + ), ) : const CircularProgressIndicator() ], diff --git a/lib/screens/authentication/sign_screen.dart b/lib/screens/authentication/sign_screen.dart new file mode 100644 index 0000000..74c3dfa --- /dev/null +++ b/lib/screens/authentication/sign_screen.dart @@ -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 createState() => _SignScreenState(); +} + +class _SignScreenState extends State 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( + 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().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( + create: (context) => + Injector.appInstance.get(), + child: SignInScreen(), + ), + BlocProvider( + create: (context) => + Injector.appInstance.get(), + child: SignUpScreen(), + ), + // BlocProvider( + // create: (context) => SignInBloc( + // userRepository: context + // .read() + // .userRepository), + // child: SignInScreen(), + // ), + // BlocProvider( + // create: (context) => SignUpBloc( + // userRepository: context + // .read() + // .userRepository), + // child: SignUpScreen(), + // ), + ]), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/authentication/sign_up_screen.dart b/lib/screens/authentication/sign_up_screen.dart index eacdb39..a9e3ae9 100644 --- a/lib/screens/authentication/sign_up_screen.dart +++ b/lib/screens/authentication/sign_up_screen.dart @@ -45,212 +45,214 @@ class _SignUpScreenState extends State { return; } }, - child: Form( - key: _formKey, - child: Center( - child: Column( - children: [ - const SizedBox(height: 20), - SizedBox( - width: MediaQuery.of(context).size.width * 0.9, - child: MyTextField( - controller: emailController, - hintText: 'Email', - obscureText: false, - keyboardType: TextInputType.emailAddress, - prefixIcon: const Icon(CupertinoIcons.mail_solid), - validator: (val) { - if (val!.isEmpty) { - return 'Please fill in this field'; - } else if (!emailRexExp.hasMatch(val)) { - return 'Please enter a valid email'; - } - return null; - }), - ), - const SizedBox(height: 10), - SizedBox( - width: MediaQuery.of(context).size.width * 0.9, - child: MyTextField( - controller: passwordController, - hintText: 'Password', - obscureText: obscurePassword, - keyboardType: TextInputType.visiblePassword, - prefixIcon: const Icon(CupertinoIcons.lock_fill), - onChanged: (val) { - if (val!.contains(RegExp(r'[A-Z]'))) { - setState(() { - containsUpperCase = true; - }); - } else { - setState(() { - containsUpperCase = false; - }); - } - if (val.contains(RegExp(r'[a-z]'))) { - setState(() { - containsLowerCase = true; - }); - } else { - setState(() { - containsLowerCase = false; - }); - } - if (val.contains(RegExp(r'[0-9]'))) { - setState(() { - containsNumber = true; - }); - } else { - setState(() { - containsNumber = false; - }); - } - if (val.contains(specialCharRexExp)) { - setState(() { - containsSpecialChar = true; - }); - } else { - setState(() { - containsSpecialChar = false; - }); - } - if (val.length >= 8) { - setState(() { - contains8Length = true; - }); - } else { - setState(() { - contains8Length = false; - }); - } - return null; - }, - suffixIcon: IconButton( - onPressed: () { - setState(() { - obscurePassword = !obscurePassword; - if (obscurePassword) { - iconPassword = CupertinoIcons.eye_fill; - } else { - iconPassword = CupertinoIcons.eye_slash_fill; - } - }); + child: Scaffold( + body: Form( + key: _formKey, + child: Center( + child: Column( + children: [ + const SizedBox(height: 20), + SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + child: MyTextField( + controller: emailController, + hintText: 'Email', + obscureText: false, + keyboardType: TextInputType.emailAddress, + prefixIcon: const Icon(CupertinoIcons.mail_solid), + validator: (val) { + if (val!.isEmpty) { + return 'Please fill in this field'; + } else if (!emailRexExp.hasMatch(val)) { + return 'Please enter a valid email'; + } + return null; + }), + ), + const SizedBox(height: 10), + SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + child: MyTextField( + controller: passwordController, + hintText: 'Password', + obscureText: obscurePassword, + keyboardType: TextInputType.visiblePassword, + prefixIcon: const Icon(CupertinoIcons.lock_fill), + onChanged: (val) { + if (val!.contains(RegExp(r'[A-Z]'))) { + setState(() { + containsUpperCase = true; + }); + } else { + setState(() { + containsUpperCase = false; + }); + } + if (val.contains(RegExp(r'[a-z]'))) { + setState(() { + containsLowerCase = true; + }); + } else { + setState(() { + containsLowerCase = false; + }); + } + if (val.contains(RegExp(r'[0-9]'))) { + setState(() { + containsNumber = true; + }); + } else { + setState(() { + containsNumber = false; + }); + } + if (val.contains(specialCharRexExp)) { + setState(() { + containsSpecialChar = true; + }); + } else { + setState(() { + containsSpecialChar = false; + }); + } + if (val.length >= 8) { + setState(() { + contains8Length = true; + }); + } else { + setState(() { + contains8Length = false; + }); + } + return null; }, - icon: Icon(iconPassword), - ), - validator: (val) { - if (val!.isEmpty) { - return 'Please fill in this field'; - } else if (!passwordRexExp.hasMatch(val)) { - return 'Please enter a valid password'; - } - 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().add(SignUpRequired( - myUser, passwordController.text)); - }); + suffixIcon: IconButton( + onPressed: () { + setState(() { + obscurePassword = !obscurePassword; + if (obscurePassword) { + iconPassword = CupertinoIcons.eye_fill; + } else { + iconPassword = CupertinoIcons.eye_slash_fill; } - }, - 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() - ], + }); + }, + icon: Icon(iconPassword), + ), + validator: (val) { + if (val!.isEmpty) { + return 'Please fill in this field'; + } else if (!passwordRexExp.hasMatch(val)) { + return 'Please enter a valid password'; + } + 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().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(), + ], + ), ), ), ), diff --git a/lib/screens/authentication/welcome_screen.dart b/lib/screens/authentication/welcome_screen.dart index 34c157a..e664995 100644 --- a/lib/screens/authentication/welcome_screen.dart +++ b/lib/screens/authentication/welcome_screen.dart @@ -1,102 +1,128 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_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'; +import 'package:flutter/services.dart'; +import 'package:intl_phone_field/intl_phone_field.dart'; +import 'package:prosappco/screens/authentication/sign_screen.dart'; -import '../../blocs/authentication_bloc/authentication_bloc.dart'; -import '../../blocs/sign_up_bloc/sign_up_bloc.dart'; - -class WelcomeScreen extends StatefulWidget { +class WelcomeScreen extends StatelessWidget { const WelcomeScreen({super.key}); - @override - State createState() => _WelcomeScreenState(); -} - -class _WelcomeScreenState extends State - with TickerProviderStateMixin { - late TabController tabController; - - @override - void initState() { - super.initState(); - tabController = TabController( - initialIndex: 0, - length: 2, - vsync: this, - ); - } - @override Widget build(BuildContext context) { + double width = MediaQuery.of(context).size.width; + return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - appBar: AppBar( - elevation: 0, - backgroundColor: Colors.transparent, - ), - body: SingleChildScrollView( - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + backgroundColor: Theme.of(context).colorScheme.surface, + body: Column( + children: [ + Container( + color: Theme.of(context).colorScheme.tertiary, child: Column( children: [ - const Text( - 'Welcome Back !', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + const SizedBox(height: 20), + Center( + child: Image( + width: width * 0.7, + image: const AssetImage('images/logo_prosapp.png'), + ), ), - const SizedBox(height: kToolbarHeight), - 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( - create: (context) => SignInBloc( - userRepository: context - .read() - .userRepository), - child: const SignInScreen(), - ), - BlocProvider( - create: (context) => SignUpBloc( - userRepository: context - .read() - .userRepository), - child: const SignUpScreen(), - ), - ]), - ) + const SizedBox(height: 20), ], ), ), - ), + 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), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ), + ], ), ); } diff --git a/lib/screens/configuration/configuration_screen.dart b/lib/screens/configuration/configuration_screen.dart new file mode 100644 index 0000000..f416f00 --- /dev/null +++ b/lib/screens/configuration/configuration_screen.dart @@ -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 createState() => _ConfigurationScreenState(); +// } + +// class _ConfigurationScreenState extends State { +// late final FirebaseAuth _auth; + +// @override +// void initState() { +// super.initState(); +// _auth = FirebaseAuth.instance; +// } + +// Future 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 _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( + 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() + .add(AuthenticationLogoutRequested()); + }, + ), + GeneralDrawerItem( + label: 'Eliminar cuenta', + onTap: () {}, + color: Theme.of(context).colorScheme.error, + ) + ]), + ), + ); + } +} diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 2f63c51..aa6046a 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -1,36 +1,18 @@ -import 'package:flutter/cupertino.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'; -class HomeScreen extends StatefulWidget { +class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { @override Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state is UploadPictureSuccess) { - setState(() { - context.read().state.user!.picture = state.userImage; - }); - } - }, - child: SafeArea( - child: Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - drawer: const GeneralDrawer(), - appBar: AppBar(), - body: const Center( - child: Text('Bienvenido'), - ), + return SafeArea( + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + drawer: GeneralDrawer(), + appBar: AppBar(), + body: const Center( + child: Text('Bienvenido'), ), ), ); diff --git a/lib/screens/profile/profile_screen.dart b/lib/screens/profile/profile_screen.dart index 9fd3113..dcc563a 100644 --- a/lib/screens/profile/profile_screen.dart +++ b/lib/screens/profile/profile_screen.dart @@ -27,9 +27,7 @@ class _ProfileScreenState extends State { return BlocListener( listener: (context, state) { if (state is UploadPictureSuccess) { - setState(() { - context.read().state.user!.picture = state.userImage; - }); + setState(() {}); } }, child: Scaffold( diff --git a/lib/src/presentation/screens/login/login.dart b/lib/src/presentation/screens/login/login.dart index d94b137..d1d8dd2 100644 --- a/lib/src/presentation/screens/login/login.dart +++ b/lib/src/presentation/screens/login/login.dart @@ -69,6 +69,30 @@ class _LoginScreenState extends State { } 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( children: [ const SizedBox( @@ -103,30 +127,7 @@ class _LoginScreenState extends State { onChanged: (phoneNo) { completePhoneNumber = phoneNo.completeNumber; }, - decoration: const 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, - ), + decoration: inputDecoration, ), ), const Text( diff --git a/packages/user_repository/lib/src/entities/city_entity.dart b/packages/user_repository/lib/src/entities/city_entity.dart new file mode 100644 index 0000000..30384e3 --- /dev/null +++ b/packages/user_repository/lib/src/entities/city_entity.dart @@ -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 toDocument() { + return { + 'name': name, + 'coords': coords, + }; + } + + static CityEntity fromDocument(Map doc) { + return CityEntity( + name: doc['name'] as String, + coords: doc['coords'] as String, + ); + } + + @override + List get props => [name, coords]; + + @override + String toString() { + return '''CityEntity { + name: $name + coords: $coords + }'''; + } +} diff --git a/packages/user_repository/lib/src/entities/country_entity.dart b/packages/user_repository/lib/src/entities/country_entity.dart new file mode 100644 index 0000000..786f449 --- /dev/null +++ b/packages/user_repository/lib/src/entities/country_entity.dart @@ -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 regions; + + const CountryEntity({ + required this.name, + required this.regions, + }); + + Map toDocument() { + return { + 'name': name, + 'regions': regions, + }; + } + + static CountryEntity fromDocument(Map doc) { + return CountryEntity( + name: doc['name'] as String, + regions: (doc['regions'] as List) + .map((region) => RegionEntity.fromDocument(region)) + .toList(), + ); + } + + @override + List get props => [name, regions]; + + @override + String toString() { + return '''CountryEntity { + name: $name + regions: $regions + }'''; + } +} diff --git a/packages/user_repository/lib/src/entities/entities.dart b/packages/user_repository/lib/src/entities/entities.dart index 60ed456..3079b2a 100644 --- a/packages/user_repository/lib/src/entities/entities.dart +++ b/packages/user_repository/lib/src/entities/entities.dart @@ -1 +1,4 @@ export 'my_user_entity.dart'; +export 'country_entity.dart'; +export 'region_entity.dart'; +export 'city_entity.dart'; diff --git a/packages/user_repository/lib/src/entities/region_entity.dart b/packages/user_repository/lib/src/entities/region_entity.dart new file mode 100644 index 0000000..9ceb3d8 --- /dev/null +++ b/packages/user_repository/lib/src/entities/region_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 cities; + + const RegionEntity({ + required this.name, + required this.cities, + }); + + Map toDocument() { + return { + 'name': name, + 'cities': cities, + }; + } + + static RegionEntity fromDocument(Map doc) { + return RegionEntity( + name: doc['name'] as String, + cities: (doc['cities'] as List) + .map((city) => CityEntity.fromDocument(city)) + .toList(), + ); + } + + @override + List get props => [name, cities]; + + @override + String toString() { + return '''RegionEntity { + name: $name + cities: $cities + }'''; + } +} diff --git a/packages/user_repository/lib/src/models/city.dart b/packages/user_repository/lib/src/models/city.dart new file mode 100644 index 0000000..c3ee139 --- /dev/null +++ b/packages/user_repository/lib/src/models/city.dart @@ -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 get props => [name, coords]; +} diff --git a/packages/user_repository/lib/src/models/country.dart b/packages/user_repository/lib/src/models/country.dart new file mode 100644 index 0000000..3ad5b24 --- /dev/null +++ b/packages/user_repository/lib/src/models/country.dart @@ -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 regions; + + const Country({ + required this.name, + required this.regions, + }); + + static const empty = Country(name: '', regions: []); + Country copyWith({ + String? name, + List? 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 get props => [name, regions]; +} diff --git a/packages/user_repository/lib/src/models/models.dart b/packages/user_repository/lib/src/models/models.dart index 29659c2..f9753f2 100644 --- a/packages/user_repository/lib/src/models/models.dart +++ b/packages/user_repository/lib/src/models/models.dart @@ -1 +1,4 @@ export 'my_user.dart'; +export 'country.dart'; +export 'region.dart'; +export 'city.dart'; diff --git a/packages/user_repository/lib/src/models/my_user.dart b/packages/user_repository/lib/src/models/my_user.dart index f9cd74e..71965c5 100644 --- a/packages/user_repository/lib/src/models/my_user.dart +++ b/packages/user_repository/lib/src/models/my_user.dart @@ -60,11 +60,3 @@ class MyUser extends Equatable { @override List 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; \ No newline at end of file diff --git a/packages/user_repository/lib/src/models/region.dart b/packages/user_repository/lib/src/models/region.dart new file mode 100644 index 0000000..1d32dae --- /dev/null +++ b/packages/user_repository/lib/src/models/region.dart @@ -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 cities; + + const Region({ + required this.name, + required this.cities, + }); + + static const empty = Region(name: '', cities: []); + Region copyWith({ + String? name, + List? 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 get props => [name, cities]; +} diff --git a/packages/user_repository/lib/src/repositories/city_repo.dart b/packages/user_repository/lib/src/repositories/city_repo.dart new file mode 100644 index 0000000..62ae295 --- /dev/null +++ b/packages/user_repository/lib/src/repositories/city_repo.dart @@ -0,0 +1 @@ +abstract class CityRepository {} diff --git a/packages/user_repository/lib/src/repositories/firebase_city_repository.dart b/packages/user_repository/lib/src/repositories/firebase_city_repository.dart new file mode 100644 index 0000000..face79b --- /dev/null +++ b/packages/user_repository/lib/src/repositories/firebase_city_repository.dart @@ -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'); +} diff --git a/packages/user_repository/lib/src/firebase_user_repository.dart b/packages/user_repository/lib/src/repositories/firebase_user_repository.dart similarity index 64% rename from packages/user_repository/lib/src/firebase_user_repository.dart rename to packages/user_repository/lib/src/repositories/firebase_user_repository.dart index 16651ac..518bf0f 100644 --- a/packages/user_repository/lib/src/firebase_user_repository.dart +++ b/packages/user_repository/lib/src/repositories/firebase_user_repository.dart @@ -6,7 +6,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:user_repository/src/models/my_user.dart'; -import 'entities/entities.dart'; +import '../entities/entities.dart'; import 'user_repo.dart'; class FirebaseUserRepository implements UserRepository { @@ -42,14 +42,6 @@ class FirebaseUserRepository implements UserRepository { return _userStreamController.stream; } - // @override - // Stream get user { - // return _firebaseAuth.authStateChanges().map((firebaseUser) { - // final user = firebaseUser; - // return user; - // }); - // } - // Sign up @override Future signUp(MyUser myUser, String password) async { @@ -70,6 +62,46 @@ class FirebaseUserRepository implements UserRepository { } } + // Sign in with phone number + @override + Future 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 @override Future signIn(String email, String password) async { diff --git a/packages/user_repository/lib/src/user_repo.dart b/packages/user_repository/lib/src/repositories/user_repo.dart similarity index 83% rename from packages/user_repository/lib/src/user_repo.dart rename to packages/user_repository/lib/src/repositories/user_repo.dart index 145ac68..953a157 100644 --- a/packages/user_repository/lib/src/user_repo.dart +++ b/packages/user_repository/lib/src/repositories/user_repo.dart @@ -1,4 +1,4 @@ -import '../user_repository.dart'; +import '../../user_repository.dart'; abstract class UserRepository { // Stream get user; @@ -10,6 +10,8 @@ abstract class UserRepository { Future signUp(MyUser myUser, String password); + Future signInWithPhoneNumber(String phoneNumber); + Future resetPassword(String email); Future setUserData(MyUser user); diff --git a/packages/user_repository/lib/user_repository.dart b/packages/user_repository/lib/user_repository.dart index 3b1f09b..e8d2c7c 100644 --- a/packages/user_repository/lib/user_repository.dart +++ b/packages/user_repository/lib/user_repository.dart @@ -2,5 +2,5 @@ library user_repository; export 'src/models/models.dart'; export 'src/entities/entities.dart'; -export 'src/user_repo.dart'; -export 'src/firebase_user_repository.dart'; +export 'src/repositories/user_repo.dart'; +export 'src/repositories/firebase_user_repository.dart'; diff --git a/pubspec.lock b/pubspec.lock index c2c34c9..9ec8de9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -853,6 +853,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7be31da..3ac66dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,9 +28,10 @@ dependencies: flutter_local_notifications: ^14.1.1 flutter_localizations: sdk: flutter - flutter_otp_text_field: null 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 geocoding: ^2.1.0 geolocator: ^9.0.2 @@ -40,8 +41,8 @@ dependencies: http: ^1.1.0 image_picker: ^1.0.7 injector: ^3.0.0 - intl: any - intl_phone_field: null + intl_phone_field: ^3.2.0 + intl: ^0.18.1 location: ^5.0.3 package_info_plus: ^4.2.0 provider: ^6.0.5