From 9ca2b62be3bdceb70e596d311ccc97cb3ae21f2e Mon Sep 17 00:00:00 2001 From: Felipe Date: Fri, 16 Feb 2024 08:52:37 -0500 Subject: [PATCH] bloc --- lib/app.dart | 19 + lib/app_view.dart | 64 ++++ .../authentication_bloc.dart | 37 ++ .../authentication_event.dart | 17 + .../authentication_state.dart | 24 ++ lib/blocs/my_user_bloc/my_user_bloc.dart | 26 ++ lib/blocs/my_user_bloc/my_user_event.dart | 17 + lib/blocs/my_user_bloc/my_user_state.dart | 23 ++ lib/blocs/sign_up_bloc/sign_up_bloc.dart | 25 ++ lib/blocs/sign_up_bloc/sign_up_event.dart | 15 + lib/blocs/sign_up_bloc/sign_up_state.dart | 16 + lib/blocs/sing_in_bloc/sign_in_bloc.dart | 31 ++ lib/blocs/sing_in_bloc/sign_in_event.dart | 19 + lib/blocs/sing_in_bloc/sign_in_state.dart | 21 ++ .../update_user_info_bloc.dart | 29 ++ .../update_user_info_event.dart | 18 + .../update_user_info_state.dart | 23 ++ lib/components/strings.dart | 7 + lib/components/textfield.dart | 61 +++ lib/main.dart | 346 +++++++++--------- .../authentication/sign_in_screen.dart | 138 +++++++ .../authentication/sign_up_screen.dart | 266 ++++++++++++++ .../authentication/welcome_screen.dart | 103 ++++++ lib/screens/home/home_screen.dart | 139 +++++++ lib/screens/profile/profile_screen.dart | 169 +++++++++ lib/simple_bloc_observer.dart | 41 +++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 - .../lib/src/entities/entities.dart | 1 + .../lib/src/entities/my_user_entity.dart | 46 +++ .../lib/src/firebase_user_repository.dart | 133 +++++++ .../lib/src/models/models.dart | 1 + .../lib/src/models/my_user.dart | 70 ++++ .../user_repository/lib/src/user_repo.dart | 23 ++ .../user_repository/lib/user_repository.dart | 6 + packages/user_repository/pubspec.lock | 338 +++++++++++++++++ packages/user_repository/pubspec.yaml | 28 ++ pubspec.lock | 155 ++++---- pubspec.yaml | 127 ++----- test/widget_test.dart | 2 +- 39 files changed, 2299 insertions(+), 327 deletions(-) create mode 100644 lib/app.dart create mode 100644 lib/app_view.dart create mode 100644 lib/blocs/authentication_bloc/authentication_bloc.dart create mode 100644 lib/blocs/authentication_bloc/authentication_event.dart create mode 100644 lib/blocs/authentication_bloc/authentication_state.dart create mode 100644 lib/blocs/my_user_bloc/my_user_bloc.dart create mode 100644 lib/blocs/my_user_bloc/my_user_event.dart create mode 100644 lib/blocs/my_user_bloc/my_user_state.dart create mode 100644 lib/blocs/sign_up_bloc/sign_up_bloc.dart create mode 100644 lib/blocs/sign_up_bloc/sign_up_event.dart create mode 100644 lib/blocs/sign_up_bloc/sign_up_state.dart create mode 100644 lib/blocs/sing_in_bloc/sign_in_bloc.dart create mode 100644 lib/blocs/sing_in_bloc/sign_in_event.dart create mode 100644 lib/blocs/sing_in_bloc/sign_in_state.dart create mode 100644 lib/blocs/update_user_info_bloc/update_user_info_bloc.dart create mode 100644 lib/blocs/update_user_info_bloc/update_user_info_event.dart create mode 100644 lib/blocs/update_user_info_bloc/update_user_info_state.dart create mode 100644 lib/components/strings.dart create mode 100644 lib/components/textfield.dart create mode 100644 lib/screens/authentication/sign_in_screen.dart create mode 100644 lib/screens/authentication/sign_up_screen.dart create mode 100644 lib/screens/authentication/welcome_screen.dart create mode 100644 lib/screens/home/home_screen.dart create mode 100644 lib/screens/profile/profile_screen.dart create mode 100644 lib/simple_bloc_observer.dart create mode 100644 packages/user_repository/lib/src/entities/entities.dart create mode 100644 packages/user_repository/lib/src/entities/my_user_entity.dart create mode 100644 packages/user_repository/lib/src/firebase_user_repository.dart create mode 100644 packages/user_repository/lib/src/models/models.dart create mode 100644 packages/user_repository/lib/src/models/my_user.dart create mode 100644 packages/user_repository/lib/src/user_repo.dart create mode 100644 packages/user_repository/lib/user_repository.dart create mode 100644 packages/user_repository/pubspec.lock create mode 100644 packages/user_repository/pubspec.yaml diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..86aa8f7 --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart'; +import 'package:user_repository/user_repository.dart'; + +import 'app_view.dart'; + +class MainApp extends StatelessWidget { + final UserRepository userRepository; + const MainApp(this.userRepository, {super.key}); + + @override + Widget build(BuildContext context) { + return MultiRepositoryProvider(providers: [ + RepositoryProvider( + create: (_) => AuthenticationBloc(myUserRepository: userRepository)) + ], child: const MyAppView()); + } +} diff --git a/lib/app_view.dart b/lib/app_view.dart new file mode 100644 index 0000000..ee1eb64 --- /dev/null +++ b/lib/app_view.dart @@ -0,0 +1,64 @@ +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/sing_in_bloc/sign_in_bloc.dart'; +import 'package:prosappco/blocs/update_user_info_bloc/update_user_info_bloc.dart'; +import 'package:prosappco/screens/authentication/welcome_screen.dart'; +import 'package:prosappco/screens/home/home_screen.dart'; +import 'package:prosappco/screens/profile/profile_screen.dart'; + +import 'blocs/authentication_bloc/authentication_bloc.dart'; + +class MyAppView extends StatelessWidget { + const MyAppView({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + 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)), + ), + home: BlocBuilder( + builder: (context, state) { + if (state.status == AuthenticationStatus.authenticated) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SignInBloc( + userRepository: + context.read().userRepository), + ), + BlocProvider( + create: (context) => UpdateUserInfoBloc( + userRepository: + context.read().userRepository), + ), + BlocProvider( + create: (context) => MyUserBloc( + myUserRepository: + context.read().userRepository) + ..add(GetMyUser( + myUserId: + context.read().state.user!.uid)), + ), + ], + child: const HomeScreen(), + ); + } else { + return const WelcomeScreen(); + } + }), + ); + } +} diff --git a/lib/blocs/authentication_bloc/authentication_bloc.dart b/lib/blocs/authentication_bloc/authentication_bloc.dart new file mode 100644 index 0000000..02c7196 --- /dev/null +++ b/lib/blocs/authentication_bloc/authentication_bloc.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:meta/meta.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'authentication_event.dart'; +part 'authentication_state.dart'; + +class AuthenticationBloc + extends Bloc { + final UserRepository userRepository; + late final StreamSubscription _userSubscription; + + AuthenticationBloc({required UserRepository myUserRepository}) + : userRepository = myUserRepository, + super(const AuthenticationState.unknown()) { + _userSubscription = userRepository.user.listen((authUser) { + add(AuthenticationUserChanged(authUser)); + }); + on((event, emit) { + if (event.user != null) { + emit(AuthenticationState.authenticated(event.user!)); + } else { + emit(const AuthenticationState.unauthenticated()); + } + }); + } + + @override + Future close() { + _userSubscription.cancel(); + return super.close(); + } +} diff --git a/lib/blocs/authentication_bloc/authentication_event.dart b/lib/blocs/authentication_bloc/authentication_event.dart new file mode 100644 index 0000000..d0a02e2 --- /dev/null +++ b/lib/blocs/authentication_bloc/authentication_event.dart @@ -0,0 +1,17 @@ +part of 'authentication_bloc.dart'; + +@immutable +abstract class AuthenticationEvent extends Equatable { + const AuthenticationEvent(); + + @override + List get props => []; +} + +class AuthenticationUserChanged extends AuthenticationEvent { + const AuthenticationUserChanged(this.user); + + final User? user; +} + +class AuthenticationLogoutRequested extends AuthenticationEvent {} diff --git a/lib/blocs/authentication_bloc/authentication_state.dart b/lib/blocs/authentication_bloc/authentication_state.dart new file mode 100644 index 0000000..4dc0957 --- /dev/null +++ b/lib/blocs/authentication_bloc/authentication_state.dart @@ -0,0 +1,24 @@ +part of 'authentication_bloc.dart'; + +enum AuthenticationStatus { authenticated, unauthenticated, unknown } + +class AuthenticationState extends Equatable { + final AuthenticationStatus status; + final User? user; + + const AuthenticationState._({ + this.status = AuthenticationStatus.unknown, + this.user, + }); + + const AuthenticationState.unknown() : this._(); + + const AuthenticationState.authenticated(User user) + : this._(status: AuthenticationStatus.authenticated, user: user); + + const AuthenticationState.unauthenticated() + : this._(status: AuthenticationStatus.unauthenticated); + + @override + List get props => [status, user]; +} diff --git a/lib/blocs/my_user_bloc/my_user_bloc.dart b/lib/blocs/my_user_bloc/my_user_bloc.dart new file mode 100644 index 0000000..bb046ee --- /dev/null +++ b/lib/blocs/my_user_bloc/my_user_bloc.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'my_user_event.dart'; +part 'my_user_state.dart'; + +class MyUserBloc extends Bloc { + final UserRepository _userRepository; + + MyUserBloc({required UserRepository myUserRepository}) + : _userRepository = myUserRepository, + super(const MyUserState.loading()) { + on(_onGetMyUser); + } + + void _onGetMyUser(GetMyUser event, Emitter emit) async { + emit(const MyUserState.loading()); + try { + MyUser myUser = await _userRepository.getMyUser(event.myUserId); + emit(MyUserState.success(myUser)); + } catch (e) { + emit(const MyUserState.failure()); + } + } +} diff --git a/lib/blocs/my_user_bloc/my_user_event.dart b/lib/blocs/my_user_bloc/my_user_event.dart new file mode 100644 index 0000000..fbe45c6 --- /dev/null +++ b/lib/blocs/my_user_bloc/my_user_event.dart @@ -0,0 +1,17 @@ +part of 'my_user_bloc.dart'; + +abstract class MyUserEvent extends Equatable { + const MyUserEvent(); + + @override + List get props => []; +} + +class GetMyUser extends MyUserEvent { + final String myUserId; + + const GetMyUser({required this.myUserId}); + + @override + List get props => [myUserId]; +} diff --git a/lib/blocs/my_user_bloc/my_user_state.dart b/lib/blocs/my_user_bloc/my_user_state.dart new file mode 100644 index 0000000..ae5fff8 --- /dev/null +++ b/lib/blocs/my_user_bloc/my_user_state.dart @@ -0,0 +1,23 @@ +part of 'my_user_bloc.dart'; + +enum MyUserStatus { success, loading, failure } + +class MyUserState extends Equatable { + final MyUserStatus status; + final MyUser? user; + + const MyUserState._({ + this.status = MyUserStatus.loading, + this.user, + }); + + const MyUserState.loading() : this._(); + + const MyUserState.success(MyUser user) + : this._(status: MyUserStatus.success, user: user); + + const MyUserState.failure() : this._(status: MyUserStatus.failure); + + @override + List get props => [status, user]; +} diff --git a/lib/blocs/sign_up_bloc/sign_up_bloc.dart b/lib/blocs/sign_up_bloc/sign_up_bloc.dart new file mode 100644 index 0000000..36d0fef --- /dev/null +++ b/lib/blocs/sign_up_bloc/sign_up_bloc.dart @@ -0,0 +1,25 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'sign_up_event.dart'; +part 'sign_up_state.dart'; + +class SignUpBloc extends Bloc { + final UserRepository _userRepository; + + 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()); + } + }); + } +} diff --git a/lib/blocs/sign_up_bloc/sign_up_event.dart b/lib/blocs/sign_up_bloc/sign_up_event.dart new file mode 100644 index 0000000..9243ab3 --- /dev/null +++ b/lib/blocs/sign_up_bloc/sign_up_event.dart @@ -0,0 +1,15 @@ +part of 'sign_up_bloc.dart'; + +abstract class SignUpEvent extends Equatable { + const SignUpEvent(); + + @override + List get props => []; +} + +class SignUpRequired extends SignUpEvent { + final MyUser user; + final String password; + + const SignUpRequired(this.user, this.password); +} diff --git a/lib/blocs/sign_up_bloc/sign_up_state.dart b/lib/blocs/sign_up_bloc/sign_up_state.dart new file mode 100644 index 0000000..6c92251 --- /dev/null +++ b/lib/blocs/sign_up_bloc/sign_up_state.dart @@ -0,0 +1,16 @@ +part of 'sign_up_bloc.dart'; + +abstract class SignUpState extends Equatable { + const SignUpState(); + + @override + List get props => []; +} + +class SignUpInitial extends SignUpState {} + +class SignUpSuccess extends SignUpState {} + +class SignUpFailure extends SignUpState {} + +class SignUpProcess extends SignUpState {} diff --git a/lib/blocs/sing_in_bloc/sign_in_bloc.dart b/lib/blocs/sing_in_bloc/sign_in_bloc.dart new file mode 100644 index 0000000..7c2c33a --- /dev/null +++ b/lib/blocs/sing_in_bloc/sign_in_bloc.dart @@ -0,0 +1,31 @@ +import 'dart:developer'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'sign_in_event.dart'; +part 'sign_in_state.dart'; + +class SignInBloc extends Bloc { + final UserRepository _userRepository; + + SignInBloc({required UserRepository userRepository}) + : _userRepository = userRepository, + super(SignInInitial()) { + on((event, emit) async { + emit(SignInProcess()); + try { + await _userRepository.signIn(event.email, event.password); + emit(SignInSuccess()); + } catch (e) { + log(e.toString()); + emit(const SignInFailure()); + } + }); + on((event, emit) async { + await _userRepository.logOut(); + }); + } +} diff --git a/lib/blocs/sing_in_bloc/sign_in_event.dart b/lib/blocs/sing_in_bloc/sign_in_event.dart new file mode 100644 index 0000000..8d10fdb --- /dev/null +++ b/lib/blocs/sing_in_bloc/sign_in_event.dart @@ -0,0 +1,19 @@ +part of 'sign_in_bloc.dart'; + +abstract class SignInEvent extends Equatable { + const SignInEvent(); + + @override + List get props => []; +} + +class SignInRequired extends SignInEvent { + final String email; + final String password; + + const SignInRequired(this.email, this.password); +} + +class SignOutRequired extends SignInEvent { + const SignOutRequired(); +} diff --git a/lib/blocs/sing_in_bloc/sign_in_state.dart b/lib/blocs/sing_in_bloc/sign_in_state.dart new file mode 100644 index 0000000..9b52dbb --- /dev/null +++ b/lib/blocs/sing_in_bloc/sign_in_state.dart @@ -0,0 +1,21 @@ +part of 'sign_in_bloc.dart'; + +@immutable +abstract class SignInState extends Equatable { + const SignInState(); + + @override + List get props => []; +} + +class SignInInitial extends SignInState {} + +class SignInSuccess extends SignInState {} + +class SignInFailure extends SignInState { + final String? message; + + const SignInFailure({this.message}); +} + +class SignInProcess extends SignInState {} diff --git a/lib/blocs/update_user_info_bloc/update_user_info_bloc.dart b/lib/blocs/update_user_info_bloc/update_user_info_bloc.dart new file mode 100644 index 0000000..d111137 --- /dev/null +++ b/lib/blocs/update_user_info_bloc/update_user_info_bloc.dart @@ -0,0 +1,29 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'update_user_info_event.dart'; +part 'update_user_info_state.dart'; + +class UpdateUserInfoBloc + extends Bloc { + final UserRepository _userRepository; + + UpdateUserInfoBloc({required UserRepository userRepository}) + : _userRepository = userRepository, + super(UpdateUserInfoInitial()) { + on(_onUploadPicture); + } + + void _onUploadPicture( + UploadPicture event, Emitter emit) async { + emit(UploadPictureLoading()); + try { + String userImage = + await _userRepository.uploadPicture(event.file, event.userId); + emit(UploadPictureSuccess(userImage)); + } catch (e) { + emit(UploadPictureFailure()); + } + } +} diff --git a/lib/blocs/update_user_info_bloc/update_user_info_event.dart b/lib/blocs/update_user_info_bloc/update_user_info_event.dart new file mode 100644 index 0000000..a08943f --- /dev/null +++ b/lib/blocs/update_user_info_bloc/update_user_info_event.dart @@ -0,0 +1,18 @@ +part of 'update_user_info_bloc.dart'; + +abstract class UpdateUserInfoEvent extends Equatable { + const UpdateUserInfoEvent(); + + @override + List get props => []; +} + +class UploadPicture extends UpdateUserInfoEvent { + final String file; + final String userId; + + const UploadPicture(this.file, this.userId); + + @override + List get props => [file, userId]; +} diff --git a/lib/blocs/update_user_info_bloc/update_user_info_state.dart b/lib/blocs/update_user_info_bloc/update_user_info_state.dart new file mode 100644 index 0000000..b765d38 --- /dev/null +++ b/lib/blocs/update_user_info_bloc/update_user_info_state.dart @@ -0,0 +1,23 @@ +part of 'update_user_info_bloc.dart'; + +abstract class UpdateUserInfoState extends Equatable { + const UpdateUserInfoState(); + + @override + List get props => []; +} + +class UpdateUserInfoInitial extends UpdateUserInfoState {} + +class UploadPictureFailure extends UpdateUserInfoState {} + +class UploadPictureLoading extends UpdateUserInfoState {} + +class UploadPictureSuccess extends UpdateUserInfoState { + final String userImage; + + const UploadPictureSuccess(this.userImage); + + @override + List get props => [userImage]; +} diff --git a/lib/components/strings.dart b/lib/components/strings.dart new file mode 100644 index 0000000..1eaccdd --- /dev/null +++ b/lib/components/strings.dart @@ -0,0 +1,7 @@ +RegExp emailRexExp = RegExp(r'^[\w-\.]+@([\w-]+.)+[\w-]{2,4}$'); + +RegExp passwordRexExp = RegExp( + r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#\$&*~`)\%\-(_+=;:,.<>/?"[{\]}\|^]).{8,}$'); + +RegExp specialCharRexExp = + RegExp(r'^(?=.*?[!@#$&*~`)\%\-(_+=;:,.<>/?"[{\]}\|^])'); diff --git a/lib/components/textfield.dart b/lib/components/textfield.dart new file mode 100644 index 0000000..abac651 --- /dev/null +++ b/lib/components/textfield.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +class MyTextField extends StatelessWidget { + final TextEditingController controller; + final String hintText; + final bool obscureText; + final TextInputType keyboardType; + final Widget? suffixIcon; + final VoidCallback? onTap; + final Widget? prefixIcon; + final String? Function(String?)? validator; + final FocusNode? focusNode; + final String? errorMsg; + final String? Function(String?)? onChanged; + + const MyTextField( + {super.key, + required this.controller, + required this.hintText, + required this.obscureText, + required this.keyboardType, + this.suffixIcon, + this.onTap, + this.prefixIcon, + this.validator, + this.focusNode, + this.errorMsg, + this.onChanged}); + + @override + Widget build(BuildContext context) { + return TextFormField( + validator: validator, + controller: controller, + obscureText: obscureText, + keyboardType: keyboardType, + focusNode: focusNode, + onTap: onTap, + textInputAction: TextInputAction.next, + onChanged: onChanged, + decoration: InputDecoration( + suffixIcon: suffixIcon, + prefixIcon: prefixIcon, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Colors.transparent), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: + BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + fillColor: Colors.grey.shade200, + filled: true, + hintText: hintText, + hintStyle: TextStyle(color: Colors.grey[500]), + errorText: errorMsg, + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 39ecfbf..c95aa4d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,172 +1,186 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:get/get.dart'; +// import 'package:intl/date_symbol_data_local.dart'; +// import 'package:prosappco/src/authentication/authentication_repository.dart'; +// import 'package:prosappco/src/presentation/screens/service_web.dart'; +// import 'package:prosappco/src/providers/user_provider.dart'; +// import 'package:prosappco/src/presentation/screens/calendar.dart'; +// import 'package:prosappco/src/presentation/screens/city.dart'; +// import 'package:prosappco/src/presentation/screens/login/login.dart'; +// import 'package:firebase_core/firebase_core.dart'; +// import 'package:prosappco/src/presentation/screens/login/login_email.dart'; +// import 'package:prosappco/src/presentation/screens/my_services.dart'; +// import 'package:prosappco/src/presentation/screens/my_services_pro.dart'; +// import 'package:prosappco/src/presentation/screens/new_number.dart'; +// import 'package:prosappco/src/presentation/screens/new_password.dart'; +// import 'package:prosappco/src/presentation/screens/profession.dart'; +// import 'package:prosappco/src/presentation/screens/professional_profile.dart'; +// import 'package:prosappco/src/presentation/screens/professional_revision.dart'; +// import 'package:prosappco/src/presentation/screens/profile/profile.dart'; +// import 'package:prosappco/src/presentation/screens/profile/profile_pro.dart'; +// import 'package:prosappco/src/presentation/screens/register/register.dart'; +// import 'package:prosappco/src/presentation/screens/request_sent.dart'; +// import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart'; +// import 'package:prosappco/src/presentation/screens/map/service.dart'; +// import 'package:prosappco/src/presentation/screens/solicitudes.dart'; +// import 'package:prosappco/src/services/local_notifications.dart'; +// import 'firebase_options.dart'; +// import 'package:firebase_messaging/firebase_messaging.dart'; +// import 'package:flutter/foundation.dart' show kIsWeb; +// import 'package:provider/provider.dart'; +// import 'package:flutter_localizations/flutter_localizations.dart'; + +// Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { +// await Firebase.initializeApp(); + +// print('Handling a backdround message: ${message.messageId}'); +// } + +// void main() async { +// WidgetsFlutterBinding.ensureInitialized(); +// await Firebase.initializeApp( +// options: DefaultFirebaseOptions.currentPlatform, +// ).then((value) => Get.put(AuthenticationRepository())); + +// // Evitar la solicitud de permiso en la versión web +// if (!kIsWeb) { +// FirebaseMessaging messaging = FirebaseMessaging.instance; + +// await messaging.requestPermission( +// alert: true, +// announcement: false, +// badge: true, +// carPlay: false, +// criticalAlert: false, +// provisional: false, +// sound: true, +// ); + +// FirebaseMessaging.onBackgroundMessage( +// (_firebaseMessagingBackgroundHandler)); + +// FirebaseMessaging.onMessage.listen((RemoteMessage message) {}); +// } + +// await initNotifications(); + +// await initializeDateFormatting('es_MX', null); + +// SystemChrome.setPreferredOrientations([ +// DeviceOrientation.portraitUp, +// DeviceOrientation.portraitDown, +// ]); + +// runApp(const MyApp()); +// } + +// class MyApp extends StatelessWidget { +// const MyApp({super.key}); + +// @override +// Widget build(BuildContext context) { +// return MultiProvider( +// providers: [ +// ChangeNotifierProvider( +// create: (_) => UserProvider(), +// ), +// ], +// child: GetMaterialApp( +// theme: ThemeData(fontFamily: 'Poppins'), +// debugShowCheckedModeBanner: false, +// localizationsDelegates: const [ +// GlobalMaterialLocalizations.delegate, +// GlobalWidgetsLocalizations.delegate, +// GlobalCupertinoLocalizations.delegate, +// ], +// supportedLocales: const [ +// Locale('es', 'US'), +// ], +// title: 'ProsApp', +// initialRoute: '/', +// routes: { +// '/': (context) => const LoginScreen(), +// '/splash': (context) => const SplashScreen(), +// '/login': (context) => const LoginEmailScreen(), +// '/profile': (context) => const ProfileScreen(), +// '/register': (context) => const RegisterScreen(), +// '/city': (context) => const CityScreen(), +// '/profession': (context) => const ProfessionScreen(), +// '/newNumber': (context) => const NewNumberScreen(), +// '/profesionalRevision': (context) => +// const ProfessionalRevisionScreen(), +// '/profesionalProfile': (context) => const ProfessionalProfileScreen(), +// '/solicitudEnviada': (context) => const RequestSentScreen(), +// '/nuevaPassword': (context) => NewPasswordScreen(), +// '/servicio': (context) => const ServiceScreen(), +// '/servicioWeb': (context) => const ServiceWebScreen(), +// '/profilePro': (context) => const ProfileProScreen(), +// '/calendar': (context) => const CalendarScreen(), +// '/solicitud': (context) => SolicitudScreen(), +// '/misserviciospro': (context) => MyServicesProScreen(), +// '/misservicios': (context) => MyServicesScreen(), +// '/resetpassword': (context) => const ResetPasswordScreen(), +// }, +// onGenerateRoute: (settings) { +// throw Exception('Ruta desconocida: ${settings.name}'); +// }, +// ), +// ); +// } +// } + +// class SplashScreen extends StatefulWidget { +// const SplashScreen({super.key}); + +// @override +// State createState() => _SplashScreenState(); +// } + +// class _SplashScreenState extends State { +// @override +// void initState() { +// super.initState(); +// if (kIsWeb) { +// // Espera 3 segundos y luego redirige a LoginScreen +// Future.delayed(const Duration(seconds: 3), () { +// Navigator.pushReplacement( +// context, +// MaterialPageRoute(builder: (context) => const LoginScreen()), +// ); +// }); +// } +// } + +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.blue, +// body: Center( +// child: kIsWeb ? Image.asset('/images/splash.png') : const LoginScreen(), +// ), +// ); +// } +// } + +// // splash: 'images/splashgif.gif', +// // backgroundColor: Colors.black, +// // nextScreen: const LoginScreen(), +// // splashIconSize: 50, +// // duration: 80000, + import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:intl/date_symbol_data_local.dart'; -import 'package:prosappco/src/authentication/authentication_repository.dart'; -import 'package:prosappco/src/presentation/screens/service_web.dart'; -import 'package:prosappco/src/providers/user_provider.dart'; -import 'package:prosappco/src/presentation/screens/calendar.dart'; -import 'package:prosappco/src/presentation/screens/city.dart'; -import 'package:prosappco/src/presentation/screens/login/login.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'package:prosappco/src/presentation/screens/login/login_email.dart'; -import 'package:prosappco/src/presentation/screens/my_services.dart'; -import 'package:prosappco/src/presentation/screens/my_services_pro.dart'; -import 'package:prosappco/src/presentation/screens/new_number.dart'; -import 'package:prosappco/src/presentation/screens/new_password.dart'; -import 'package:prosappco/src/presentation/screens/profession.dart'; -import 'package:prosappco/src/presentation/screens/professional_profile.dart'; -import 'package:prosappco/src/presentation/screens/professional_revision.dart'; -import 'package:prosappco/src/presentation/screens/profile/profile.dart'; -import 'package:prosappco/src/presentation/screens/profile/profile_pro.dart'; -import 'package:prosappco/src/presentation/screens/register/register.dart'; -import 'package:prosappco/src/presentation/screens/request_sent.dart'; -import 'package:prosappco/src/presentation/screens/reset_password/reset_password.dart'; -import 'package:prosappco/src/presentation/screens/map/service.dart'; -import 'package:prosappco/src/presentation/screens/solicitudes.dart'; -import 'package:prosappco/src/services/local_notifications.dart'; -import 'firebase_options.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:provider/provider.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; - -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - await Firebase.initializeApp(); - - print('Handling a backdround message: ${message.messageId}'); -} +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:user_repository/user_repository.dart'; +import 'app.dart'; +import 'simple_bloc_observer.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, - ).then((value) => Get.put(AuthenticationRepository())); - - // Evitar la solicitud de permiso en la versión web - if (!kIsWeb) { - FirebaseMessaging messaging = FirebaseMessaging.instance; - - await messaging.requestPermission( - alert: true, - announcement: false, - badge: true, - carPlay: false, - criticalAlert: false, - provisional: false, - sound: true, - ); - - FirebaseMessaging.onBackgroundMessage( - (_firebaseMessagingBackgroundHandler)); - - FirebaseMessaging.onMessage.listen((RemoteMessage message) {}); - } - - await initNotifications(); - - await initializeDateFormatting('es_MX', null); - - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - DeviceOrientation.portraitDown, - ]); - - runApp(const MyApp()); + await Firebase.initializeApp(); + Bloc.observer = SimpleBlocObserver(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + runApp(MainApp(FirebaseUserRepository())); } - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (_) => UserProvider(), - ), - ], - child: GetMaterialApp( - theme: ThemeData(fontFamily: 'Poppins'), - debugShowCheckedModeBanner: false, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('es', 'US'), - ], - title: 'ProsApp', - initialRoute: '/', - routes: { - '/': (context) => const LoginScreen(), - '/splash': (context) => const SplashScreen(), - '/login': (context) => const LoginEmailScreen(), - '/profile': (context) => const ProfileScreen(), - '/register': (context) => const RegisterScreen(), - '/city': (context) => const CityScreen(), - '/profession': (context) => const ProfessionScreen(), - '/newNumber': (context) => const NewNumberScreen(), - '/profesionalRevision': (context) => - const ProfessionalRevisionScreen(), - '/profesionalProfile': (context) => const ProfessionalProfileScreen(), - '/solicitudEnviada': (context) => const RequestSentScreen(), - '/nuevaPassword': (context) => NewPasswordScreen(), - '/servicio': (context) => const ServiceScreen(), - '/servicioWeb': (context) => const ServiceWebScreen(), - '/profilePro': (context) => const ProfileProScreen(), - '/calendar': (context) => const CalendarScreen(), - '/solicitud': (context) => SolicitudScreen(), - '/misserviciospro': (context) => MyServicesProScreen(), - '/misservicios': (context) => MyServicesScreen(), - '/resetpassword': (context) => const ResetPasswordScreen(), - }, - onGenerateRoute: (settings) { - throw Exception('Ruta desconocida: ${settings.name}'); - }, - ), - ); - } -} - -class SplashScreen extends StatefulWidget { - const SplashScreen({super.key}); - - @override - State createState() => _SplashScreenState(); -} - -class _SplashScreenState extends State { - @override - void initState() { - super.initState(); - if (kIsWeb) { - // Espera 3 segundos y luego redirige a LoginScreen - Future.delayed(const Duration(seconds: 3), () { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const LoginScreen()), - ); - }); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.blue, - body: Center( - child: kIsWeb ? Image.asset('/images/splash.png') : const LoginScreen(), - ), - ); - } -} - - - - // splash: 'images/splashgif.gif', - // backgroundColor: Colors.black, - // nextScreen: const LoginScreen(), - // splashIconSize: 50, - // duration: 80000, \ No newline at end of file diff --git a/lib/screens/authentication/sign_in_screen.dart b/lib/screens/authentication/sign_in_screen.dart new file mode 100644 index 0000000..eaa2bc8 --- /dev/null +++ b/lib/screens/authentication/sign_in_screen.dart @@ -0,0 +1,138 @@ +import 'package:flutter/cupertino.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 '../../components/strings.dart'; +import '../../components/textfield.dart'; + +class SignInScreen extends StatefulWidget { + const SignInScreen({super.key}); + + @override + State createState() => _SignInScreenState(); +} + +class _SignInScreenState extends State { + final _formKey = GlobalKey(); + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + String? _errorMsg; + bool obscurePassword = true; + IconData iconPassword = CupertinoIcons.eye_fill; + bool signInRequired = false; + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state is SignInSuccess) { + setState(() { + signInRequired = false; + }); + } else if (state is SignInProcess) { + setState(() { + signInRequired = true; + }); + } else if (state is SignInFailure) { + setState(() { + signInRequired = false; + _errorMsg = 'Invalid email or password'; + }); + } + }, + child: Form( + key: _formKey, + 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), + errorMsg: _errorMsg, + 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), + errorMsg: _errorMsg, + validator: (val) { + if (val!.isEmpty) { + return 'Please fill in this field'; + } else if (!passwordRexExp.hasMatch(val)) { + return 'Please enter a valid password'; + } + return null; + }, + suffixIcon: IconButton( + onPressed: () { + setState(() { + obscurePassword = !obscurePassword; + if (obscurePassword) { + iconPassword = CupertinoIcons.eye_fill; + } else { + iconPassword = CupertinoIcons.eye_slash_fill; + } + }); + }, + icon: Icon(iconPassword), + ), + ), + ), + const SizedBox(height: 20), + !signInRequired + ? SizedBox( + 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), + ), + )), + ) + : const CircularProgressIndicator() + ], + )), + ); + } +} diff --git a/lib/screens/authentication/sign_up_screen.dart b/lib/screens/authentication/sign_up_screen.dart new file mode 100644 index 0000000..70a814b --- /dev/null +++ b/lib/screens/authentication/sign_up_screen.dart @@ -0,0 +1,266 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../blocs/sign_up_bloc/sign_up_bloc.dart'; +import '../../components/strings.dart'; +import '../../components/textfield.dart'; + +class SignUpScreen extends StatefulWidget { + const SignUpScreen({super.key}); + + @override + State createState() => _SignUpScreenState(); +} + +class _SignUpScreenState extends State { + final _formKey = GlobalKey(); + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + bool obscurePassword = true; + IconData iconPassword = CupertinoIcons.eye_fill; + final nameController = TextEditingController(); + bool signUpRequired = false; + + bool containsUpperCase = false; + bool containsLowerCase = false; + bool containsNumber = false; + bool containsSpecialChar = false; + bool contains8Length = false; + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state is SignUpSuccess) { + setState(() { + signUpRequired = false; + }); + } else if (state is SignUpProcess) { + setState(() { + signUpRequired = true; + }); + } else if (state is SignUpFailure) { + 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; + } + }); + }, + 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), + ), + Text( + "⚈ 1 number", + style: TextStyle( + color: containsNumber + ? Colors.green + : Theme.of(context).colorScheme.onBackground), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "⚈ 1 special character", + style: TextStyle( + color: containsSpecialChar + ? Colors.green + : Theme.of(context).colorScheme.onBackground), + ), + Text( + "⚈ 8 minimum character", + style: TextStyle( + color: contains8Length + ? 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 new file mode 100644 index 0000000..34c157a --- /dev/null +++ b/lib/screens/authentication/welcome_screen.dart @@ -0,0 +1,103 @@ +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 '../../blocs/authentication_bloc/authentication_bloc.dart'; +import '../../blocs/sign_up_bloc/sign_up_bloc.dart'; + +class WelcomeScreen extends StatefulWidget { + 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) { + 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), + child: Column( + children: [ + const Text( + 'Welcome Back !', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + 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(), + ), + ]), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart new file mode 100644 index 0000000..c763ba2 --- /dev/null +++ b/lib/screens/home/home_screen.dart @@ -0,0 +1,139 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:prosappco/blocs/my_user_bloc/my_user_bloc.dart'; +import 'package:prosappco/blocs/sing_in_bloc/sign_in_bloc.dart'; +import 'package:prosappco/blocs/update_user_info_bloc/update_user_info_bloc.dart'; +import 'package:prosappco/screens/profile/profile_screen.dart'; + +class HomeScreen extends StatefulWidget { + 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: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + drawer: BlocBuilder(builder: (context, state) { + if (state.status == MyUserStatus.success) { + return Drawer( + backgroundColor: Theme.of(context).colorScheme.background, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Material( + color: Theme.of(context).colorScheme.primary, + child: InkWell( + onTap: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const ProfileScreen(), + ), + ); + }, + child: Container( + padding: EdgeInsets.only( + top: 20 + MediaQuery.of(context).padding.top, + bottom: 20, + ), + child: Column( + children: [ + 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: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.grey, + shape: BoxShape.circle, + image: DecorationImage( + image: NetworkImage( + state.user!.picture!, + ), + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(height: 12), + Text( + context.read().state.user!.name, + style: const TextStyle( + fontSize: 24, + color: Colors.white, + ), + ), + Text( + state.user!.email, + style: const TextStyle( + fontSize: 15, + color: Colors.white, + ), + ) + ], + ), + ), + ), + ), + 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', + ), + ) + ], + ), + ) + ], + ), + ), + ); + } else { + return Drawer( + backgroundColor: Theme.of(context).colorScheme.background, + child: const Center( + child: CircularProgressIndicator(), + )); + } + }), + appBar: AppBar(), + body: const Placeholder(), + ), + ); + } +} diff --git a/lib/screens/profile/profile_screen.dart b/lib/screens/profile/profile_screen.dart new file mode 100644 index 0000000..aa592bd --- /dev/null +++ b/lib/screens/profile/profile_screen.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:prosappco/blocs/authentication_bloc/authentication_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/src/components/pop_appbar.dart'; + +class ProfileScreen extends StatefulWidget { + const ProfileScreen({super.key}); + + @override + State createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => MyUserBloc( + myUserRepository: + context.read().userRepository) + ..add(GetMyUser( + myUserId: context.read().state.user!.uid)), + ), + BlocProvider( + create: (context) => UpdateUserInfoBloc( + userRepository: context.read().userRepository, + ), + ) + ], + child: BlocListener( + listener: (context, state) { + if (state is UploadPictureSuccess) { + setState(() { + context.read().state.user!.picture = state.userImage; + }); + } + }, + child: Scaffold( + appBar: PopAppbar( + onPressed: () { + Navigator.pop(context); + }, + label: 'Perfil', + ), + body: + BlocBuilder(builder: (context, state) { + if (state.status == MyUserStatus.success) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GestureDetector( + onTap: () async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: ImageSource.gallery, + maxHeight: 500, + maxWidth: 500, + imageQuality: 40, + ); + + if (image != null) { + context.read().add( + UploadPicture(image.path, state.user!.id), + ); + } + }, + child: state.user!.picture == "" + ? Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), + child: Icon( + CupertinoIcons.person, + color: Colors.grey.shade400, + size: 40, + ), + ) + : Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Colors.grey, + shape: BoxShape.circle, + image: DecorationImage( + image: NetworkImage( + state.user!.picture!, + ), + fit: BoxFit.cover, + ), + ), + ), + ), + const SizedBox(height: 20.0), + TextFormField( + initialValue: state.user?.name, + decoration: const InputDecoration( + labelText: 'Nombre', + prefixIcon: Icon(Icons.person), + hintText: 'Nombre (obligatorio)', + border: OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(25.0), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Colors.red, width: 2.0), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Por favor, ingrese su nombre'; + } + return null; + }, + ), + const SizedBox(height: 20.0), + TextFormField( + initialValue: state.user?.email, + decoration: const InputDecoration( + labelText: 'Nombre', + prefixIcon: Icon(Icons.person), + hintText: 'Nombre (obligatorio)', + border: OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(25.0), + )), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Colors.red, width: 2.0), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Por favor, ingrese su nombre'; + } + return null; + }, + ), + ElevatedButton( + onPressed: () {}, + child: const Text('Guardar'), + ), + ], + ), + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + })), + ), + ); + } +} diff --git a/lib/simple_bloc_observer.dart b/lib/simple_bloc_observer.dart new file mode 100644 index 0000000..369fc27 --- /dev/null +++ b/lib/simple_bloc_observer.dart @@ -0,0 +1,41 @@ +import 'dart:developer'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SimpleBlocObserver extends BlocObserver { + @override + void onCreate(BlocBase bloc) { + super.onCreate(bloc); + log('onCreate -- bloc: ${bloc.runtimeType}'); + } + + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + log('onEvent -- bloc: ${bloc.runtimeType}, event: $event'); + } + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + log('onChange -- bloc: ${bloc.runtimeType}, change: $change'); + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + log('onTransition -- bloc: ${bloc.runtimeType}, transition: $transition'); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + log('onError -- bloc: ${bloc.runtimeType}, error: $error'); + super.onError(bloc, error, stackTrace); + } + + @override + void onClose(BlocBase bloc) { + super.onClose(bloc); + log('onClose -- bloc: ${bloc.runtimeType}'); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7ebb5b5..eb92bbb 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,6 @@ import cloud_firestore import file_selector_macos import firebase_auth import firebase_core -import firebase_messaging import firebase_storage import flutter_local_notifications import geolocator_apple @@ -23,7 +22,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) - FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) diff --git a/packages/user_repository/lib/src/entities/entities.dart b/packages/user_repository/lib/src/entities/entities.dart new file mode 100644 index 0000000..60ed456 --- /dev/null +++ b/packages/user_repository/lib/src/entities/entities.dart @@ -0,0 +1 @@ +export 'my_user_entity.dart'; diff --git a/packages/user_repository/lib/src/entities/my_user_entity.dart b/packages/user_repository/lib/src/entities/my_user_entity.dart new file mode 100644 index 0000000..cc0a4a5 --- /dev/null +++ b/packages/user_repository/lib/src/entities/my_user_entity.dart @@ -0,0 +1,46 @@ +import 'package:equatable/equatable.dart'; + +class MyUserEntity extends Equatable { + final String id; + final String email; + final String name; + final String? picture; + + const MyUserEntity({ + required this.id, + required this.email, + required this.name, + this.picture, + }); + + Map toDocument() { + return { + 'id': id, + 'email': email, + 'name': name, + 'picture': picture, + }; + } + + static MyUserEntity fromDocument(Map doc) { + return MyUserEntity( + id: doc['id'] as String, + email: doc['email'] as String, + name: doc['name'] as String, + picture: doc['picture'] as String?, + ); + } + + @override + List get props => [id, email, name, picture]; + + @override + String toString() { + return '''UserEntity { + id: $id + email: $email + name: $name + picture: $picture + }'''; + } +} diff --git a/packages/user_repository/lib/src/firebase_user_repository.dart b/packages/user_repository/lib/src/firebase_user_repository.dart new file mode 100644 index 0000000..952203a --- /dev/null +++ b/packages/user_repository/lib/src/firebase_user_repository.dart @@ -0,0 +1,133 @@ +import 'dart:developer'; +import 'dart:io'; + +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 'user_repo.dart'; + +class FirebaseUserRepository implements UserRepository { + FirebaseUserRepository({ + FirebaseAuth? firebaseAuth, + }) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; + + final FirebaseAuth _firebaseAuth; + final usersCollection = FirebaseFirestore.instance.collection('users'); + + @override + Stream get user { + return _firebaseAuth.authStateChanges().map((firebaseUser) { + final user = firebaseUser; + return user; + }); + } + + // Sign up + @override + Future signUp(MyUser myUser, String password) async { + try { + UserCredential user = await _firebaseAuth.createUserWithEmailAndPassword( + email: myUser.email, + password: password, + ); + + myUser = myUser.copyWith( + id: user.user!.uid, + ); + + return myUser; + } catch (e) { + log(e.toString()); + rethrow; + } + } + + // Sign in + @override + Future signIn(String email, String password) async { + try { + await _firebaseAuth.signInWithEmailAndPassword( + email: email, + password: password, + ); + } catch (e) { + log(e.toString()); + rethrow; + } + } + + // Sign out + @override + Future logOut() async { + try { + await _firebaseAuth.signOut(); + } catch (e) { + log(e.toString()); + rethrow; + } + } + + // Reset password + @override + Future resetPassword(String email) async { + try { + await _firebaseAuth.sendPasswordResetEmail(email: email); + } catch (e) { + log(e.toString()); + rethrow; + } + } + + // Set user data + @override + Future setUserData(MyUser user) async { + try { + await usersCollection.doc(user.id).set(user.toEntity().toDocument()); + } catch (e) { + log(e.toString()); + rethrow; + } + } + + // Get user data + @override + Future getMyUser(String myUserId) async { + try { + return usersCollection.doc(myUserId).get().then((value) => + MyUser.fromEntity(MyUserEntity.fromDocument(value.data()!))); + } catch (e) { + log(e.toString()); + rethrow; + } + } + + @override + Future updateUserInfo(String userId, Map data) async { + try { + await usersCollection.doc(userId).update(data); + } catch (e) { + log(e.toString()); + rethrow; + } + } + + @override + Future uploadPicture(String file, String userId) async { + try { + File imageFile = File(file); + Reference firebaseStoreRef = + FirebaseStorage.instance.ref().child('$userId/PP/${userId}_lead'); + await firebaseStoreRef.putFile( + imageFile, + ); + String url = await firebaseStoreRef.getDownloadURL(); + await usersCollection.doc(userId).update({'picture': url}); + return url; + } catch (e) { + log(e.toString()); + rethrow; + } + } +} diff --git a/packages/user_repository/lib/src/models/models.dart b/packages/user_repository/lib/src/models/models.dart new file mode 100644 index 0000000..29659c2 --- /dev/null +++ b/packages/user_repository/lib/src/models/models.dart @@ -0,0 +1 @@ +export 'my_user.dart'; diff --git a/packages/user_repository/lib/src/models/my_user.dart b/packages/user_repository/lib/src/models/my_user.dart new file mode 100644 index 0000000..f9cd74e --- /dev/null +++ b/packages/user_repository/lib/src/models/my_user.dart @@ -0,0 +1,70 @@ +import 'package:equatable/equatable.dart'; + +import '../entities/entities.dart'; + +class MyUser extends Equatable { + final String id; + final String email; + final String name; + String? picture; + + MyUser({ + required this.id, + required this.email, + required this.name, + this.picture, + }); + + /// Empty user which represents an unauthenticated user. + static final empty = MyUser(id: '', email: '', name: '', picture: ''); + + /// Modify MyUser parameters + MyUser copyWith({ + String? id, + String? email, + String? name, + String? picture, + }) { + return MyUser( + id: id ?? this.id, + email: email ?? this.email, + name: name ?? this.name, + picture: picture ?? this.picture, + ); + } + + /// Convenience getter to determine whether the current user is empty. + bool get isEmpty => this == MyUser.empty; + + /// Convenience getter to determine whether the current user is not empty. + bool get isNotEmpty => this != MyUser.empty; + + MyUserEntity toEntity() { + return MyUserEntity( + id: id, + email: email, + name: name, + picture: picture, + ); + } + + static MyUser fromEntity(MyUserEntity entity) { + return MyUser( + id: entity.id, + email: entity.email, + name: entity.name, + picture: entity.picture, + ); + } + + @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/user_repo.dart b/packages/user_repository/lib/src/user_repo.dart new file mode 100644 index 0000000..74cd3c6 --- /dev/null +++ b/packages/user_repository/lib/src/user_repo.dart @@ -0,0 +1,23 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +import '../user_repository.dart'; + +abstract class UserRepository { + Stream get user; + + Future signIn(String email, String password); + + Future logOut(); + + Future signUp(MyUser myUser, String password); + + Future resetPassword(String email); + + Future setUserData(MyUser user); + + Future getMyUser(String myUserId); + + Future updateUserInfo(String userId, Map data); + + Future uploadPicture(String file, String userId); +} diff --git a/packages/user_repository/lib/user_repository.dart b/packages/user_repository/lib/user_repository.dart new file mode 100644 index 0000000..3b1f09b --- /dev/null +++ b/packages/user_repository/lib/user_repository.dart @@ -0,0 +1,6 @@ +library user_repository; + +export 'src/models/models.dart'; +export 'src/entities/entities.dart'; +export 'src/user_repo.dart'; +export 'src/firebase_user_repository.dart'; diff --git a/packages/user_repository/pubspec.lock b/packages/user_repository/pubspec.lock new file mode 100644 index 0000000..edeb5e1 --- /dev/null +++ b/packages/user_repository/pubspec.lock @@ -0,0 +1,338 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "1a52f1afae8ab7ac4741425114713bdbba802f1ce1e0648e167ffcc6e05e96cf" + url: "https://pub.dev" + source: hosted + version: "1.3.21" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: b62be7f11ba72fd6112d8921336d670023a6784898e5cf4d21754cc114d8b56c + url: "https://pub.dev" + source: hosted + version: "4.15.4" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "53f34ec3b6e90537786bfeabc5be1798e03518b5ffbf8acef4d64523f517b010" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: "85367362561333e40d48ce60351b0d9e58f457fad8d06e36ae5d94f1b9b29518" + url: "https://pub.dev" + source: hosted + version: "3.10.4" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "549f8ceb8cfc1920f85dea0ab73fb7dc209ee8182916b252eda342786c33369d" + url: "https://pub.dev" + source: hosted + version: "4.17.4" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "83bfc14649f673db17ad0bffaa0222019f99f3ddf499bcc8b46e1eb3443d3e08" + url: "https://pub.dev" + source: hosted + version: "7.1.4" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: d2266452698dd5f6e522408dacfa06bb7f9703b5bdd11498fce2812ded50805b + url: "https://pub.dev" + source: hosted + version: "5.9.4" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445" + url: "https://pub.dev" + source: hosted + version: "2.25.4" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + url: "https://pub.dev" + source: hosted + version: "5.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba" + url: "https://pub.dev" + source: hosted + version: "2.11.4" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: b87029b506972987a827feaf296c21cd0fe1bb69c2595be1672253ba5205573e + url: "https://pub.dev" + source: hosted + version: "11.6.5" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: "180822103b164d0d597131f2fb658cd1c438148abafc6f2256b565227303ba35" + url: "https://pub.dev" + source: hosted + version: "5.1.8" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: "9523c455521b0497ee436be8614aab52f719309d16147a5b11091e44e4c5aa0a" + url: "https://pub.dev" + source: hosted + version: "3.6.22" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + url: "https://pub.dev" + source: hosted + version: "1.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" +sdks: + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.3.0" diff --git a/packages/user_repository/pubspec.yaml b/packages/user_repository/pubspec.yaml new file mode 100644 index 0000000..1e4ce26 --- /dev/null +++ b/packages/user_repository/pubspec.yaml @@ -0,0 +1,28 @@ +name: user_repository +description: Dart package which manages the user. +publish_to: "none" + +version: 1.0.11+11 + +environment: + sdk: ">=2.19.3 <3.0.0" + +dependencies: + flutter: + sdk: flutter + equatable: ^2.0.5 + + # Firebase + firebase_auth: ^4.17.4 + cloud_firestore: ^4.15.4 + firebase_storage: ^11.6.5 + firebase_core: ^2.25.4 + + +dev_dependencies: + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/pubspec.lock b/pubspec.lock index f1f7255..dd2661d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "5dadadeecceac19d6a63c9d2e037bb8df58ddd4aedb94e8a056af2f39ee50f9d" + sha256: "1a52f1afae8ab7ac4741425114713bdbba802f1ce1e0648e167ffcc6e05e96cf" url: "https://pub.dev" source: hosted - version: "1.3.11" + version: "1.3.21" analyzer: dependency: transitive description: @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + url: "https://pub.dev" + source: hosted + version: "8.1.3" boolean_selector: dependency: transitive description: @@ -93,34 +101,34 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: "3ee12bfde22251a91e46bcb86e5ebe3f35006b8f6ed7fd5a5526cfa188ce68e9" + sha256: b62be7f11ba72fd6112d8921336d670023a6784898e5cf4d21754cc114d8b56c url: "https://pub.dev" source: hosted - version: "4.12.2" + version: "4.15.4" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: ed65b9d615d70c5b921e7f028ddab9b06c627d52a58d65fd84c3181f23cc9171 + sha256: "53f34ec3b6e90537786bfeabc5be1798e03518b5ffbf8acef4d64523f517b010" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.1.5" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: "17fbdc86611ed8ae414e65de7f45b19a7a2f70ce3cf9a9370ffed68844a73156" + sha256: "85367362561333e40d48ce60351b0d9e58f457fad8d06e36ae5d94f1b9b29518" url: "https://pub.dev" source: hosted - version: "3.8.3" + version: "3.10.4" collection: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" community_material_icon: dependency: "direct main" description: @@ -185,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -253,34 +269,34 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "738c4225bf8e766750423abcaeab1dc45f1bdb8975e2b32b69d561350b124685" + sha256: "549f8ceb8cfc1920f85dea0ab73fb7dc209ee8182916b252eda342786c33369d" url: "https://pub.dev" source: hosted - version: "4.12.1" + version: "4.17.4" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "40f759021591e3ce5741e981ae7488903dd31cab59aa19a663a8255511d58f95" + sha256: "83bfc14649f673db17ad0bffaa0222019f99f3ddf499bcc8b46e1eb3443d3e08" url: "https://pub.dev" source: hosted - version: "7.0.3" + version: "7.1.4" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: e068327d62503c32e2b3fbcc27991f6d8a687e2fdb60e9ad865835208b2a29ed + sha256: d2266452698dd5f6e522408dacfa06bb7f9703b5bdd11498fce2812ded50805b url: "https://pub.dev" source: hosted - version: "5.8.6" + version: "5.9.4" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "7706f4ade6cc2698c70074083bc262586a185047f6bfdd53938dcc35d35cbb9e" + sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445" url: "https://pub.dev" source: hosted - version: "2.21.0" + version: "2.25.4" firebase_core_platform_interface: dependency: transitive description: @@ -293,58 +309,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "0631a2ec971dbc540275e2fa00c3a8a2676f0a7adbc3c197d6fba569db689d97" + sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba" url: "https://pub.dev" source: hosted - version: "2.8.1" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: "53952a6f7860c44429bec80719c411e0ff77ce6cf31fade1515c7bdd87abe4a1" - url: "https://pub.dev" - source: hosted - version: "14.7.3" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "543390d1c76aaf3fa563de223d1732a5def5a5efe31428e43a44e9a47efc5ed3" - url: "https://pub.dev" - source: hosted - version: "4.5.12" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: ecfe4e851652dc5f40f1e42efcac0c2bc6bf5ef08faca936c94e1472c247d222 - url: "https://pub.dev" - source: hosted - version: "3.5.12" + version: "2.11.4" firebase_storage: - dependency: "direct main" + dependency: transitive description: name: firebase_storage - sha256: "4cce7efd856dfb98b0e0ffe3ee22039133f940e3a3cb00bfa7ce42bfebc17311" + sha256: b87029b506972987a827feaf296c21cd0fe1bb69c2595be1672253ba5205573e url: "https://pub.dev" source: hosted - version: "11.4.1" + version: "11.6.5" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface - sha256: f8cd0343fbf0b53fd5d85172cdb3311206bf71500d538e29941f440cc034a0aa + sha256: "180822103b164d0d597131f2fb658cd1c438148abafc6f2256b565227303ba35" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.1.8" firebase_storage_web: dependency: transitive description: name: firebase_storage_web - sha256: ea654cf19e2cb60acfd90e3f439aedbb36852b37b0c4521fbbd8569c80d65624 + sha256: "9523c455521b0497ee436be8614aab52f719309d16147a5b11091e44e4c5aa0a" url: "https://pub.dev" source: hosted - version: "3.6.12" + version: "3.6.22" flutter: dependency: "direct main" description: flutter @@ -358,6 +350,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.0+1" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + url: "https://pub.dev" + source: hosted + version: "8.1.4" flutter_dialogs: dependency: "direct main" description: @@ -681,10 +681,10 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" + sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.7" image_picker_android: dependency: transitive description: @@ -745,10 +745,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" intl_phone_field: dependency: "direct main" description: @@ -809,26 +809,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -1062,26 +1062,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -1118,10 +1118,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" timezone: dependency: transitive description: @@ -1218,6 +1218,13 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + user_repository: + dependency: "direct main" + description: + path: "packages/user_repository" + relative: true + source: path + version: "1.0.11+11" uuid: dependency: transitive description: @@ -1242,6 +1249,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" webview_flutter: dependency: "direct main" description: @@ -1307,5 +1322,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0e39bd5..e226435 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,115 +1,71 @@ name: prosappco description: Encuentra personal médico certificado. -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. +publish_to: "none" + version: 1.0.11+11 environment: - sdk: '>=2.19.3 <3.0.0' + sdk: ">=2.19.3 <3.0.0" -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: + animate_do: ^3.0.2 + animated_splash_screen: ^1.3.0 + cloud_firestore: null + community_material_icon: ^5.9.55 + cupertino_icons: ^1.0.2 + diacritic: null + equatable: ^2.0.5 + file_picker: ^5.3.2 + firebase_auth: ^4.17.4 + firebase_core: ^2.25.4 flutter: sdk: flutter - intl: any + flutter_animate: null + flutter_bloc: ^8.1.4 + flutter_dialogs: ^3.0.0 + flutter_email_sender: ^5.2.0 + flutter_local_notifications: ^14.1.1 flutter_localizations: sdk: flutter - shared_preferences: ^2.0.10 - cupertino_icons: ^1.0.2 - location: ^5.0.3 + flutter_otp_text_field: null flutter_polyline_points: ^2.0.0 - google_maps_flutter: ^2.5.0 - firebase_auth: ^4.6.2 - firebase_core: ^2.13.1 - file_picker: ^5.3.2 - google_sign_in: ^6.1.4 - provider: ^6.0.5 - package_info_plus: ^4.2.0 - get: + flutter_rating_bar: null font_awesome_flutter: ^10.4.0 - flutter_otp_text_field: - intl_phone_field: - cloud_firestore: - diacritic: - image_picker: ^1.0.4 - flutter_animate: - flutter_rating_bar: - table_calendar: - http: ^1.1.0 - geolocator: ^9.0.2 geocoding: ^2.1.0 - url_launcher: ^6.1.10 - community_material_icon: ^5.9.55 - flutter_email_sender: ^5.2.0 - webview_flutter: ^4.4.1 - firebase_storage: ^11.2.2 + geolocator: ^9.0.2 + get: null + google_maps_flutter: ^2.5.0 + google_sign_in: ^6.1.4 + http: ^1.1.0 + image_picker: ^1.0.7 + intl: any + intl_phone_field: null + location: ^5.0.3 + package_info_plus: ^4.2.0 + provider: ^6.0.5 responsive_builder: ^0.7.0 - flutter_local_notifications: ^14.1.1 - flutter_dialogs: ^3.0.0 - universal_html: ^2.2.3 - firebase_messaging: ^14.6.3 - animated_splash_screen: ^1.3.0 - animate_do: ^3.0.2 + shared_preferences: ^2.0.10 shimmer: ^3.0.0 - + table_calendar: null + universal_html: ^2.2.3 + url_launcher: ^6.1.10 + user_repository: + path: packages/user_repository + webview_flutter: ^4.4.1 dev_dependencies: + flutter_lints: ^2.0.0 flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - images/ - # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: fonts: - family: Poppins fonts: @@ -122,8 +78,3 @@ flutter: - asset: fonts/Poppins-SemiBold.ttf - asset: fonts/Poppins-Bold.ttf - asset: fonts/Poppins-ExtraBold.ttf - # style: italic - - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart index f6e88ca..4ba0a11 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:prosappco/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const MainApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);