This commit is contained in:
Felipe
2024-02-19 18:16:51 -05:00
parent 9e6f76e309
commit 997c6047bb
34 changed files with 1235 additions and 594 deletions
+7 -7
View File
@@ -25,16 +25,16 @@ class MainApp extends StatelessWidget {
BlocProvider<UpdateUserInfoBloc>(
create: (context) => Injector.appInstance.get<UpdateUserInfoBloc>(),
),
BlocProvider<SignInBloc>(
create: (context) => Injector.appInstance.get<SignInBloc>(),
),
BlocProvider<SignUpBloc>(
create: (context) => Injector.appInstance.get<SignUpBloc>(),
)
// BlocProvider<SignInBloc>(
// create: (context) => Injector.appInstance.get<SignInBloc>(),
// ),
// BlocProvider<SignUpBloc>(
// create: (context) => Injector.appInstance.get<SignUpBloc>(),
// ),
],
child: BlocBuilder<MyUserBloc, MyUserState>(
builder: (context, state) {
return const MyAppView();
return const SafeArea(child: MyAppView());
},
),
);
+24 -19
View File
@@ -16,29 +16,34 @@ class MyAppView extends StatelessWidget {
title: 'Prosappco',
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<AuthenticationBloc, AuthenticationState>(
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();
}
}
}
@@ -19,13 +19,23 @@ class AuthenticationBloc
_userSubscription = userRepository.streamUser().listen((authUser) {
add(AuthenticationUserChanged(authUser));
});
on<AuthenticationUserChanged>((event, emit) {
if (event.user != null) {
emit(AuthenticationState.authenticated(event.user!));
} else {
emit(const AuthenticationState.unauthenticated());
}
});
on<AuthenticationUserChanged>(_onAuthenticationUserChanged);
on<AuthenticationLogoutRequested>(_onAuthenticationLogoutRequested);
}
void _onAuthenticationUserChanged(
AuthenticationUserChanged event, Emitter<AuthenticationState> emit) {
emit(
event.user != null
? AuthenticationState.authenticated(event.user!)
: const AuthenticationState.unauthenticated(),
);
}
void _onAuthenticationLogoutRequested(AuthenticationLogoutRequested event,
Emitter<AuthenticationState> emit) async {
await userRepository.logOut();
emit(const AuthenticationState.unauthenticated());
}
@override
+13 -10
View File
@@ -11,15 +11,18 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
SignUpBloc({required UserRepository userRepository})
: _userRepository = userRepository,
super(SignUpInitial()) {
on<SignUpRequired>((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<SignUpRequired>(_onSignUpRequired);
}
void _onSignUpRequired(
SignUpRequired event, Emitter<SignUpState> emit) async {
emit(SignUpProcess());
try {
MyUser user = await _userRepository.signUp(event.user, event.password);
await _userRepository.setUserData(user);
emit(SignUpSuccess());
} catch (e) {
emit(SignUpFailure());
}
}
}
+25 -8
View File
@@ -14,18 +14,35 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
SignInBloc({required UserRepository userRepository})
: _userRepository = userRepository,
super(SignInInitial()) {
on<SignInRequired>((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<SignOutRequired>((event, emit) async {
await _userRepository.logOut();
});
on<SignInRequired>(_onSignInRequired);
on<SignOutRequired>(_onSignOutRequired);
}
void _onSignInRequired(
SignInRequired event, Emitter<SignInState> emit) async {
emit(SignInProcess());
try {
await _userRepository.signIn(event.email, event.password);
emit(SignInSuccess());
} catch (e) {
log(e.toString());
emit(const SignInFailure());
}
}
void _onSignOutRequired(
SignOutRequired event, Emitter<SignInState> emit) async {
await _userRepository.logOut();
}
}
+75 -80
View File
@@ -1,96 +1,91 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/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<GeneralDrawer> createState() => _GeneralDrawerState();
}
class _GeneralDrawerState extends State<GeneralDrawer> {
@override
Widget build(BuildContext context) {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>(
listener: (context, state) {
if (state is UploadPictureSuccess) {
setState(() {
context.read<MyUserBloc>().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<SignInBloc>().add(const SignOutRequired());
// },
// title: const Text(
// 'Cerrar Sesión',
// ),
// )
// ],
// ),
// )
],
),
),
const SizedBox(height: 15),
],
),
);
}
+47 -62
View File
@@ -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<GeneralDrawerHeader> createState() => _GeneralDrawerHeaderState();
}
class _GeneralDrawerHeaderState extends State<GeneralDrawerHeader> {
@override
Widget build(BuildContext context) {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>(
listener: (context, state) {
if (state is UploadPictureSuccess) {
setState(() {
context.read<MyUserBloc>().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<MyUserBloc>().state.user!.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
context.read<MyUserBloc>().state.user!.email,
style: const TextStyle(fontSize: 12),
),
leading: context.read<MyUserBloc>().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<MyUserBloc>().state.user!.picture!,
),
fit: BoxFit.cover,
title: Text(
context.read<MyUserBloc>().state.user!.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
context.read<MyUserBloc>().state.user!.email,
style: const TextStyle(fontSize: 12),
),
leading: context.read<MyUserBloc>().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<MyUserBloc>().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),
);
}
}
+19 -7
View File
@@ -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),
),
);
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class GeneralPrimaryButton extends StatelessWidget {
const GeneralPrimaryButton({super.key});
@override
Widget build(BuildContext context) {
return Container();
}
}
+2 -2
View File
@@ -23,10 +23,10 @@ class UserDI {
injector.registerSingleton<UpdateUserInfoBloc>((() =>
UpdateUserInfoBloc(userRepository: injector.get<UserRepository>())));
injector.registerSingleton<SignInBloc>(
injector.registerDependency<SignInBloc>(
(() => SignInBloc(userRepository: injector.get<UserRepository>())));
injector.registerSingleton<SignUpBloc>(
injector.registerDependency<SignUpBloc>(
(() => SignUpBloc(userRepository: injector.get<UserRepository>())));
}
}
+26 -25
View File
@@ -103,32 +103,33 @@ class _SignInScreenState extends State<SignInScreen> {
width: MediaQuery.of(context).size.width * 0.9,
height: 50,
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
context.read<SignInBloc>().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<SignInBloc>().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()
],
+131
View File
@@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injector/injector.dart';
import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:prosappco/blocs/sign_up_bloc/sign_up_bloc.dart';
import 'package:prosappco/blocs/sing_in_bloc/sign_in_bloc.dart';
import 'package:prosappco/screens/authentication/sign_in_screen.dart';
import 'package:prosappco/screens/authentication/sign_up_screen.dart';
class SignScreen extends StatefulWidget {
final int initialIndex;
SignScreen({super.key, required this.initialIndex});
@override
State<SignScreen> createState() => _SignScreenState();
}
class _SignScreenState extends State<SignScreen> with TickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(
initialIndex: widget.initialIndex,
length: 2,
vsync: this,
);
}
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
Navigator.pop(context);
}
},
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.tertiary,
),
body: Column(
children: [
// Text('${context.read<SignInBloc>().state}'),
Container(
color: Theme.of(context).colorScheme.tertiary,
child: Column(
children: [
Center(
child: Image(
width: width * 0.7,
image: AssetImage('images/logo_prosapp.png'),
),
),
SizedBox(height: 20),
TabBar(
controller: tabController,
unselectedLabelColor:
Theme.of(context).colorScheme.onBackground,
labelColor: Theme.of(context).colorScheme.onBackground,
tabs: [
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Inicia sesión',
style: TextStyle(
fontSize: 18,
),
),
),
Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Registrate',
style: TextStyle(
fontSize: 18,
),
),
),
],
),
],
),
),
Expanded(
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
Expanded(
child: TabBarView(controller: tabController, children: [
BlocProvider<SignInBloc>(
create: (context) =>
Injector.appInstance.get<SignInBloc>(),
child: SignInScreen(),
),
BlocProvider<SignUpBloc>(
create: (context) =>
Injector.appInstance.get<SignUpBloc>(),
child: SignUpScreen(),
),
// BlocProvider<SignInBloc>(
// create: (context) => SignInBloc(
// userRepository: context
// .read<AuthenticationBloc>()
// .userRepository),
// child: SignInScreen(),
// ),
// BlocProvider<SignUpBloc>(
// create: (context) => SignUpBloc(
// userRepository: context
// .read<AuthenticationBloc>()
// .userRepository),
// child: SignUpScreen(),
// ),
]),
)
],
),
),
),
),
],
),
),
);
}
}
+206 -204
View File
@@ -45,212 +45,214 @@ class _SignUpScreenState extends State<SignUpScreen> {
return;
}
},
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<SignUpBloc>().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<SignUpBloc>().add(SignUpRequired(
myUser, passwordController.text));
});
}
},
style: TextButton.styleFrom(
elevation: 3.0,
backgroundColor:
Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(60))),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 25, vertical: 5),
child: Text(
'Sign Up',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600),
),
)),
)
: const CircularProgressIndicator(),
],
),
),
),
),
+112 -86
View File
@@ -1,102 +1,128 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_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<WelcomeScreen> createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen>
with TickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this,
);
}
@override
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<SignInBloc>(
create: (context) => SignInBloc(
userRepository: context
.read<AuthenticationBloc>()
.userRepository),
child: const SignInScreen(),
),
BlocProvider<SignUpBloc>(
create: (context) => SignUpBloc(
userRepository: context
.read<AuthenticationBloc>()
.userRepository),
child: const SignUpScreen(),
),
]),
)
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),
),
);
},
),
],
),
),
],
),
),
),
],
),
);
}
@@ -0,0 +1,171 @@
// import 'package:cloud_firestore/cloud_firestore.dart';
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:prosappco/src/components/pop_appbar.dart';
// import 'package:prosappco/src/presentation/screens/about.dart';
// class ConfigurationScreen extends StatefulWidget {
// const ConfigurationScreen({super.key});
// @override
// State<ConfigurationScreen> createState() => _ConfigurationScreenState();
// }
// class _ConfigurationScreenState extends State<ConfigurationScreen> {
// late final FirebaseAuth _auth;
// @override
// void initState() {
// super.initState();
// _auth = FirebaseAuth.instance;
// }
// Future<void> deleteAccount() async {
// try {
// final currentUser = _auth.currentUser;
// if (currentUser != null) {
// final uid = currentUser.uid;
// await FirebaseFirestore.instance.collection('users').doc(uid).delete();
// await currentUser.delete();
// await _auth.signOut();
// Get.snackbar(
// 'Cuenta Eliminada',
// 'Tu cuenta ha sido eliminada con éxito.',
// snackPosition: SnackPosition.BOTTOM,
// );
// }
// } catch (e) {
// Get.snackbar(
// 'Error al Eliminar Cuenta',
// 'Hubo un error al eliminar tu cuenta. Por favor, inténtalo de nuevo más tarde.',
// snackPosition: SnackPosition.BOTTOM,
// );
// }
// }
// Future<void> _showDeleteAccountConfirmationDialog(
// BuildContext context) async {
// return showDialog(
// context: context,
// builder: (BuildContext context) {
// return AlertDialog(
// title: const Text('Eliminar Cuenta'),
// content: const Text(
// '¿Estás seguro de que deseas eliminar tu cuenta? Esta acción no se puede deshacer.'),
// actions: [
// TextButton(
// onPressed: () {
// Navigator.of(context).pop();
// },
// child: const Text('Cancelar'),
// ),
// TextButton(
// onPressed: () {
// deleteAccount();
// Navigator.of(context).pop();
// },
// child: const Text(
// 'Eliminar',
// style:
// TextStyle(color: Colors.red, fontWeight: FontWeight.w600),
// ),
// ),
// ],
// );
// },
// );
// }
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: PopAppbar(
// onPressed: () {
// Navigator.pop(context);
// },
// label: 'Configuración'),
// body: ListView(
// children: [
// ListTile(
// onTap: () {
// Navigator.push(
// context,
// CupertinoPageRoute(
// builder: (BuildContext context) {
// return const AboutScreen();
// },
// ),
// );
// },
// title: const Text('Acerca de la aplicación'),
// trailing: const Icon(
// Icons.keyboard_arrow_right,
// color: Colors.black,
// ),
// ),
// ListTile(
// onTap: () {
// _showDeleteAccountConfirmationDialog(context);
// },
// title: const Text(
// 'Eliminar cuenta',
// style: TextStyle(color: Colors.red),
// ),
// ),
// ],
// ),
// );
// }
// }
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:prosappco/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:prosappco/components/general_drawer_item.dart';
class ConfigurationScreen extends StatelessWidget {
const ConfigurationScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state.status == AuthenticationStatus.unauthenticated) {
Navigator.pop(context);
}
},
child: Scaffold(
appBar: AppBar(
title: const Text('Configuración'),
),
body: ListView(children: [
GeneralDrawerItem(
label: 'Acerca de la aplicación',
onTap: () {},
trailing: true,
),
GeneralDrawerItem(
label: 'Cerrar sesión',
onTap: () {
context
.read<AuthenticationBloc>()
.add(AuthenticationLogoutRequested());
},
),
GeneralDrawerItem(
label: 'Eliminar cuenta',
onTap: () {},
color: Theme.of(context).colorScheme.error,
)
]),
),
);
}
}
+8 -26
View File
@@ -1,36 +1,18 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_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<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>(
listener: (context, state) {
if (state is UploadPictureSuccess) {
setState(() {
context.read<MyUserBloc>().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'),
),
),
);
+1 -3
View File
@@ -27,9 +27,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
return BlocListener<UpdateUserInfoBloc, UpdateUserInfoState>(
listener: (context, state) {
if (state is UploadPictureSuccess) {
setState(() {
context.read<MyUserBloc>().state.user!.picture = state.userImage;
});
setState(() {});
}
},
child: Scaffold(
+25 -24
View File
@@ -69,6 +69,30 @@ class _LoginScreenState extends State<LoginScreen> {
}
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<LoginScreen> {
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(