diff --git a/assets/logo_auth.png b/assets/logo_auth.png new file mode 100644 index 0000000..02db7d7 Binary files /dev/null and b/assets/logo_auth.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/presentation/authentication/desktop/login_view_desktop.dart b/lib/presentation/authentication/desktop/login_view_desktop.dart index 990128f..12eacef 100644 --- a/lib/presentation/authentication/desktop/login_view_desktop.dart +++ b/lib/presentation/authentication/desktop/login_view_desktop.dart @@ -7,6 +7,7 @@ import 'package:monumento/application/authentication/login_register/login_regist import 'package:monumento/service_locator.dart'; import 'package:monumento/utils/app_colors.dart'; import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; class LoginViewDesktop extends StatefulWidget { const LoginViewDesktop({super.key}); @@ -125,70 +126,27 @@ class _LoginViewDesktopState extends State { const SizedBox( height: 22, ), - TextFormField( - controller: emailController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter email.'; - } else if (!value.contains('@')) { - return 'Please enter a valid email.'; - } - return null; - }, - autovalidateMode: - AutovalidateMode.onUserInteraction, - decoration: InputDecoration( - labelText: 'Email', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + emailController, 'Email', false, (value) { + if (value == null || value.isEmpty) { + return 'Please enter email.'; + } else if (!value.contains('@')) { + return 'Please enter a valid email.'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 16, ), - TextFormField( - controller: passwordController, - obscureText: true, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter password.'; - } else if (value.length < 6) { - return 'Password must be at least 6 characters.'; - } - return null; - }, - autovalidateMode: - AutovalidateMode.onUserInteraction, - decoration: InputDecoration( - labelText: 'Password', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + passwordController, 'Password', true, (value) { + if (value == null || value.isEmpty) { + return 'Please enter password.'; + } else if (value.length < 6) { + return 'Password must be at least 6 characters.'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 16, ), @@ -216,17 +174,8 @@ class _LoginViewDesktopState extends State { height: 48, ), SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - backgroundColor: AppColor.appPrimary, - padding: - const EdgeInsets.symmetric(vertical: 20), - ), - onPressed: () { + width: double.infinity, + child: CustomUI.customElevatedButton(() { if (formKey.currentState!.validate()) { locator().add( LoginWithEmailPressed( @@ -248,16 +197,7 @@ class _LoginViewDesktopState extends State { ), ); } - }, - child: Text( - 'Login', - style: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - ), - ), - ), + }, 'Login')), const SizedBox( height: 26, ), diff --git a/lib/presentation/authentication/desktop/onboarding_view_desktop.dart b/lib/presentation/authentication/desktop/onboarding_view_desktop.dart index 319fac4..9ac5f39 100644 --- a/lib/presentation/authentication/desktop/onboarding_view_desktop.dart +++ b/lib/presentation/authentication/desktop/onboarding_view_desktop.dart @@ -8,7 +8,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:monumento/application/authentication/login_register/login_register_bloc.dart'; import 'package:monumento/service_locator.dart'; import 'package:monumento/utils/app_colors.dart'; -import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; class OnboardingViewDesktop extends StatefulWidget { const OnboardingViewDesktop({super.key}); @@ -94,116 +94,48 @@ class _OnboardingViewDesktopState extends State { image = img; }); }, - child: CircleAvatar( - radius: 40, - backgroundColor: AppColor.appGreyAccent, - child: image != null - ? Image.file(File(image!.path)) - : SvgPicture.asset( + child: image != null + ? CircleAvatar( + radius: 40, + backgroundImage: + FileImage(File(image!.path))) + : CircleAvatar( + radius: 40, + backgroundColor: AppColor.appGreyAccent, + child: SvgPicture.asset( 'assets/icons/ic_user.svg', ), - ), + ), ), const SizedBox( height: 22, ), - TextFormField( - controller: nameController, - decoration: InputDecoration( - labelText: 'Name', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + nameController, 'Name', false, null, null), const SizedBox( height: 16, ), - TextFormField( - controller: usernameController, - decoration: InputDecoration( - labelText: 'Username', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField(usernameController, + 'Username', false, null, null), const SizedBox( height: 16, ), - TextFormField( - controller: statusController, - decoration: InputDecoration( - labelText: 'Status', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + statusController, 'Status', false, null, null), const SizedBox( height: 48, ), SizedBox( width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + child: CustomUI.customElevatedButton(() { + locator().add( + SaveOnboardingDetails( + name: nameController.text, + status: statusController.text, + username: usernameController.text, ), - backgroundColor: AppColor.appPrimary, - padding: - const EdgeInsets.symmetric(vertical: 20), - ), - onPressed: () { - locator().add( - SaveOnboardingDetails( - name: nameController.text, - status: statusController.text, - username: usernameController.text, - ), - ); - }, - child: Text( - 'Continue', - style: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - ), - ), + ); + }, 'Continue'), ), const SizedBox( height: 26, diff --git a/lib/presentation/authentication/desktop/reset_password_view_desktop.dart b/lib/presentation/authentication/desktop/reset_password_view_desktop.dart index 70288dd..7f76b7a 100644 --- a/lib/presentation/authentication/desktop/reset_password_view_desktop.dart +++ b/lib/presentation/authentication/desktop/reset_password_view_desktop.dart @@ -6,6 +6,7 @@ import 'package:monumento/application/authentication/login_register/login_regist import 'package:monumento/service_locator.dart'; import 'package:monumento/utils/app_colors.dart'; import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; class ResetPasswordViewDesktop extends StatefulWidget { const ResetPasswordViewDesktop({super.key}); @@ -97,93 +98,38 @@ class _ResetPasswordViewDesktopState extends State { ), SizedBox( width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - backgroundColor: AppColor.appPrimary, - padding: const EdgeInsets.symmetric( - vertical: 20), - ), - onPressed: () { - context.pop(); - }, - child: Text( - 'Back to Login', - style: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - ), - ), + child: CustomUI.customElevatedButton(() { + context.pop(); + }, 'Back to Login'), ), ], ); } return Column( children: [ - TextFormField( - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your email'; - } else if (!value.contains('@')) { - return 'Please enter a valid email'; - } - return null; - }, - autovalidateMode: - AutovalidateMode.onUserInteraction, - controller: emailController, - decoration: InputDecoration( - labelText: 'Email', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + emailController, 'Email', false, (value) { + if (value == null || value.isEmpty) { + return 'Please enter email.'; + } else if (!value.contains('@')) { + return 'Please enter a valid email.'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 38, ), SizedBox( width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - backgroundColor: AppColor.appPrimary, - padding: - const EdgeInsets.symmetric(vertical: 20), - ), - onPressed: () { - if (formKey.currentState!.validate()) { - locator().add( - ResetPasswordButtonPressed( - email: emailController.text, - ), - ); - } - }, - child: Text( - 'Reset Password', - style: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - ), - ), + child: CustomUI.customElevatedButton(() { + if (formKey.currentState!.validate()) { + locator().add( + ResetPasswordButtonPressed( + email: emailController.text, + ), + ); + } + }, 'Reset Password'), ), const SizedBox( height: 26, diff --git a/lib/presentation/authentication/desktop/sign_up_view_desktop.dart b/lib/presentation/authentication/desktop/sign_up_view_desktop.dart index 11a216d..333e97a 100644 --- a/lib/presentation/authentication/desktop/sign_up_view_desktop.dart +++ b/lib/presentation/authentication/desktop/sign_up_view_desktop.dart @@ -10,6 +10,7 @@ import 'package:monumento/application/authentication/login_register/login_regist import 'package:monumento/service_locator.dart'; import 'package:monumento/utils/app_colors.dart'; import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; import 'widgets/sign_up_decider_widget.dart'; @@ -143,208 +144,97 @@ class _SignUpViewDesktopState extends State image = img; }); }, - child: CircleAvatar( - radius: 40, - backgroundColor: AppColor.appGreyAccent, - child: image != null - ? Image.file(File(image!.path)) - : SvgPicture.asset( + child: image != null + ? CircleAvatar( + radius: 40, + backgroundImage: + FileImage(File(image!.path))) + : CircleAvatar( + radius: 40, + backgroundColor: + AppColor.appGreyAccent, + child: SvgPicture.asset( 'assets/icons/ic_user.svg', ), - ), + ), ), const SizedBox( height: 22, ), - TextFormField( - controller: nameController, - validator: (value) { - if (value!.isEmpty) { - return 'Name cannot be empty'; - } - return null; - }, - autovalidateMode: - AutovalidateMode.onUserInteraction, - decoration: InputDecoration( - labelText: 'Name', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + nameController, 'Name', false, (value) { + if (value!.isEmpty) { + return 'Name cannot be empty'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 16, ), - TextFormField( - controller: usernameController, - validator: (value) { - if (value!.isEmpty) { - return 'Username cannot be empty'; - } - return null; - }, - decoration: InputDecoration( - labelText: 'Username', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + usernameController, 'Username', false, + (value) { + if (value!.isEmpty) { + return 'Username cannot be empty'; + } + return null; + }, null), const SizedBox( height: 16, ), - TextFormField( - controller: statusController, - validator: (value) { - if (value!.isEmpty) { - return 'Status cannot be empty'; - } - return null; - }, - autovalidateMode: - AutovalidateMode.onUserInteraction, - decoration: InputDecoration( - labelText: 'Status', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + statusController, 'Status', false, (value) { + if (value!.isEmpty) { + return 'Status cannot be empty'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 16, ), - TextFormField( - controller: emailController, - validator: (value) { - if (value!.isEmpty) { - return 'Email cannot be empty'; - } else if (!value.contains('@')) { - return 'Invalid email'; - } - return null; - }, - decoration: InputDecoration( - labelText: 'Email', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + emailController, 'Email', false, (value) { + if (value!.isEmpty) { + return 'Email cannot be empty'; + } else if (!value.contains('@')) { + return 'Invalid email'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 16, ), - TextFormField( - controller: passwordController, - obscureText: true, - validator: (value) { - if (value!.isEmpty) { - return 'Password cannot be empty'; - } else if (value.length < 6) { - return 'Password must be at least 6 characters'; - } - return null; - }, - decoration: InputDecoration( - labelText: 'Password', - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondary, - ), - ), - floatingLabelStyle: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - border: const OutlineInputBorder( - borderSide: BorderSide( - color: AppColor.appSecondaryBlack, - ), - ), - ), - ), + CustomUI.customTextField( + passwordController, 'Password', true, + (value) { + if (value!.isEmpty) { + return 'Password cannot be empty'; + } else if (value.length < 6) { + return 'Password must be at least 6 characters'; + } + return null; + }, AutovalidateMode.onUserInteraction), const SizedBox( height: 48, ), SizedBox( width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - backgroundColor: AppColor.appPrimary, - padding: const EdgeInsets.symmetric( - vertical: 20), - ), - onPressed: () { - if (formKey.currentState!.validate()) { - locator().add( - SignUpWithEmailPressed( - email: emailController.text, - password: passwordController.text, - name: nameController.text, - username: usernameController.text, - status: statusController.text, - profilePictureFile: image != null - ? File(image!.path) - : null, - ), - ); - } - }, - child: Text( - 'Sign Up', - style: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - ), - ), + child: CustomUI.customElevatedButton(() { + if (formKey.currentState!.validate()) { + locator().add( + SignUpWithEmailPressed( + email: emailController.text, + password: passwordController.text, + name: nameController.text, + username: usernameController.text, + status: statusController.text, + profilePictureFile: image != null + ? File(image!.path) + : null, + ), + ); + } + }, 'Sign Up'), ), const SizedBox( height: 26, diff --git a/lib/presentation/authentication/desktop/widgets/sign_up_decider_widget.dart b/lib/presentation/authentication/desktop/widgets/sign_up_decider_widget.dart index 218d241..b6f8d54 100644 --- a/lib/presentation/authentication/desktop/widgets/sign_up_decider_widget.dart +++ b/lib/presentation/authentication/desktop/widgets/sign_up_decider_widget.dart @@ -3,6 +3,7 @@ import 'package:flutter_signin_button/flutter_signin_button.dart'; import 'package:go_router/go_router.dart'; import 'package:monumento/utils/app_colors.dart'; import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; class SignUpDeciderWidget extends StatefulWidget { final VoidCallback onSignUpWithEmailPressed; @@ -26,7 +27,7 @@ class _SignUpDeciderWidgetState extends State { width: double.infinity, child: SignInButton( padding: const EdgeInsets.all(4), - Buttons.GoogleDark, + Buttons.Google, text: 'Sign up with Google', onPressed: widget.onSignUpWithGooglePressed, ), @@ -47,23 +48,8 @@ class _SignUpDeciderWidgetState extends State { ), SizedBox( width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - backgroundColor: AppColor.appPrimary, - padding: const EdgeInsets.symmetric(vertical: 20), - ), - onPressed: widget.onSignUpWithEmailPressed, - child: Text( - 'Sign up with Email', - style: AppTextStyles.s14( - color: AppColor.appSecondary, - fontType: FontType.MEDIUM, - ), - ), - ), + child: CustomUI.customElevatedButton( + widget.onSignUpWithEmailPressed, 'Sign up with Email'), ), const SizedBox( height: 22, diff --git a/lib/presentation/authentication/mobile/login_view_mobile.dart b/lib/presentation/authentication/mobile/login_view_mobile.dart index 92c250c..0632b1e 100644 --- a/lib/presentation/authentication/mobile/login_view_mobile.dart +++ b/lib/presentation/authentication/mobile/login_view_mobile.dart @@ -1,15 +1,243 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_signin_button/button_list.dart'; +import 'package:flutter_signin_button/button_view.dart'; +import 'package:go_router/go_router.dart'; +import 'package:monumento/application/authentication/login_register/login_register_bloc.dart'; +import 'package:monumento/service_locator.dart'; +import 'package:monumento/utils/app_colors.dart'; +import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; -class LoginViewMobile extends StatelessWidget { +class LoginViewMobile extends StatefulWidget { const LoginViewMobile({super.key}); + @override + State createState() => _LoginViewMobileState(); +} + +class _LoginViewMobileState extends State { + late TextEditingController emailController; + late TextEditingController passwordController; + GlobalKey formKey = GlobalKey(); + + @override + void initState() { + emailController = TextEditingController(); + passwordController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + emailController.dispose(); + passwordController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Scaffold( - backgroundColor: Colors.white, - body: Center( - child: Text('Login View Mobile'), - ), - ); + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: AppColor.appBackground, + body: Center( + child: SizedBox( + width: 411, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset(width: 136, height: 105, 'assets/logo_auth.png'), + const SizedBox( + width: 390, + height: 40, + ), + Form( + key: formKey, + child: Center( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 32), + width: 380, + child: + BlocListener( + bloc: locator(), + listener: (context, state) { + if (state is LoginFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + state.message, + style: AppTextStyles.s14( + color: AppColor.appWhite, + fontType: FontType.MEDIUM, + ), + ), + backgroundColor: AppColor.appSecondary, + ), + ); + } + }, + child: BlocBuilder( + bloc: locator(), + builder: (context, state) { + if (state is LoginRegisterLoading) { + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + AppColor.appPrimary, + ), + ), + ); + } + if (state is SigninWithGoogleSuccess) { + while (context.canPop() == true) { + context.pop(); + } + context.push('/'); + } + return Column( + children: [ + SizedBox( + width: double.infinity, + child: SignInButton( + padding: const EdgeInsets.all(4), + Buttons.Google, + onPressed: () { + locator().add( + LoginWithGooglePressed(), + ); + }, + ), + ), + const SizedBox( + height: 15, + ), + const Text( + 'Or', + style: TextStyle( + color: AppColor.appSecondaryBlack, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox( + height: 15, + ), + CustomUI.customTextField( + emailController, 'Email', false, (value) { + if (value == null || value.isEmpty) { + return 'Please enter email.'; + } else if (!value.contains('@')) { + return 'Please enter a valid email.'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 16, + ), + CustomUI.customTextField( + passwordController, 'Password', true, + (value) { + if (value == null || value.isEmpty) { + return 'Please enter password.'; + } else if (value.length < 6) { + return 'Password must be at least 6 characters.'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 4, + ), + Align( + alignment: Alignment.bottomRight, + child: TextButton( + onPressed: () { + context.push('/reset-password'); + }, + style: ButtonStyle( + overlayColor: WidgetStateProperty.all( + Colors.transparent, + ), + ), + child: Text( + 'Forgot Password?', + style: AppTextStyles.s14( + color: AppColor.appSecondary, + fontType: FontType.MEDIUM, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: double.infinity, + child: CustomUI.customElevatedButton(() { + if (formKey.currentState!.validate()) { + locator().add( + LoginWithEmailPressed( + email: emailController.text, + password: passwordController.text, + ), + ); + } else { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Please enter valid email and password', + style: AppTextStyles.s14( + color: AppColor.appWhite, + fontType: FontType.MEDIUM, + ), + ), + backgroundColor: + AppColor.appSecondary, + ), + ); + } + }, 'Login')), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Don\'t have an account?', + style: AppTextStyles.s14( + color: AppColor.appSecondaryBlack, + fontType: FontType.REGULAR, + ), + ), + TextButton( + onPressed: () { + context.push('/register'); + }, + style: ButtonStyle( + overlayColor: WidgetStateProperty.all( + Colors.transparent, + ), + ), + child: Text( + 'Sign Up', + style: AppTextStyles.s14( + color: AppColor.appPrimary, + fontType: FontType.MEDIUM, + ), + ), + ), + ], + ), + ], + ); + }, + ), + ), + ), + ), + ) + ]), + ), + )); } } diff --git a/lib/presentation/authentication/mobile/onboarding_view_mobile.dart b/lib/presentation/authentication/mobile/onboarding_view_mobile.dart index be6aa3a..2d8c5d7 100644 --- a/lib/presentation/authentication/mobile/onboarding_view_mobile.dart +++ b/lib/presentation/authentication/mobile/onboarding_view_mobile.dart @@ -1,14 +1,155 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:monumento/application/authentication/login_register/login_register_bloc.dart'; +import 'package:monumento/service_locator.dart'; +import 'package:monumento/utils/app_colors.dart'; +import 'package:monumento/utils/constants.dart'; -class OnboardingViewMobile extends StatelessWidget { +class OnboardingViewMobile extends StatefulWidget { const OnboardingViewMobile({super.key}); + @override + State createState() => _OnboardingViewMobileState(); +} + +class _OnboardingViewMobileState extends State { + late TextEditingController nameController; + late TextEditingController usernameController; + late TextEditingController statusController; + late PageController controller; + XFile? image; + + @override + void initState() { + nameController = TextEditingController(); + usernameController = TextEditingController(); + statusController = TextEditingController(); + controller = PageController(); + super.initState(); + } + + @override + void dispose() { + nameController.dispose(); + usernameController.dispose(); + statusController.dispose(); + controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Scaffold( - backgroundColor: Colors.white, - body: Center( - child: Text('Onboarding View Mobile'), + return Scaffold( + backgroundColor: AppColor.appBackground, + body: Form( + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 64, + ), + SvgPicture.asset( + 'assets/desktop/logo_desktop.svg', + width: 220, + ), + const Spacer(), + Card( + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 50), + width: 380, + child: BlocBuilder( + bloc: locator(), + builder: (context, state) { + if (state is LoginRegisterLoading) { + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + AppColor.appPrimary, + ), + ), + ); + } + if (state is SignUpSuccess) { + context.go('/'); + } + return Column( + children: [ + InkWell( + onTap: () async { + final ImagePicker picker = ImagePicker(); + final img = await picker.pickImage( + source: ImageSource.gallery, + ); + setState(() { + image = img; + }); + }, + child: image != null + ? CircleAvatar( + radius: 40, + backgroundImage: + FileImage(File(image!.path))) + : CircleAvatar( + radius: 40, + backgroundColor: AppColor.appGreyAccent, + child: SvgPicture.asset( + 'assets/icons/ic_user.svg', + ), + ), + ), + const SizedBox( + height: 22, + ), + CustomUI.customTextField( + nameController, 'Name', false, null, null), + const SizedBox( + height: 16, + ), + CustomUI.customTextField(usernameController, + 'Username', false, null, null), + const SizedBox( + height: 16, + ), + CustomUI.customTextField( + statusController, 'Status', false, null, null), + const SizedBox( + height: 48, + ), + SizedBox( + width: double.infinity, + child: CustomUI.customElevatedButton(() { + locator().add( + SaveOnboardingDetails( + name: nameController.text, + status: statusController.text, + username: usernameController.text, + ), + ); + }, 'Continue'), + ), + const SizedBox( + height: 26, + ), + ], + ); + }, + ), + ), + ), + const Spacer(), + ], + ), + ), ), ); } diff --git a/lib/presentation/authentication/mobile/reset_password_view_mobile.dart b/lib/presentation/authentication/mobile/reset_password_view_mobile.dart index 45666ca..5050d86 100644 --- a/lib/presentation/authentication/mobile/reset_password_view_mobile.dart +++ b/lib/presentation/authentication/mobile/reset_password_view_mobile.dart @@ -1,15 +1,179 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:monumento/application/authentication/login_register/login_register_bloc.dart'; +import 'package:monumento/service_locator.dart'; +import 'package:monumento/utils/app_colors.dart'; +import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; -class ResetPasswordViewMobile extends StatelessWidget { +class ResetPasswordViewMobile extends StatefulWidget { const ResetPasswordViewMobile({super.key}); + @override + State createState() => + _ResetPasswordViewMobileState(); +} + +class _ResetPasswordViewMobileState extends State { + late TextEditingController emailController; + GlobalKey formKey = GlobalKey(); + @override + void initState() { + emailController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + emailController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Scaffold( - backgroundColor: Colors.white, - body: Center( - child: Text('Reset Password View Mobile'), - ), - ); + return Scaffold( + backgroundColor: Colors.white, + body: Scaffold( + backgroundColor: AppColor.appBackground, + body: Form( + key: formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox( + height: 24, + ), + Image.asset( + 'assets/logo_auth.png', + height: 105, + width: 136, + ), + const SizedBox( + height: 24, + ), + Center( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 32, vertical: 50), + width: 380, + child: BlocListener( + bloc: locator(), + listener: (context, state) { + if (state is ResetPasswordSuccess) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Password reset link has been sent to your email.', + ), + ), + ); + } else if (state is ResetPasswordFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + ), + ); + } + }, + child: BlocBuilder( + bloc: locator(), + builder: (context, state) { + if (state is LoginRegisterLoading) { + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + AppColor.appPrimary, + ), + ), + ); + } + if (state is ResetPasswordSuccess) { + return Column( + children: [ + Text( + "Password rest link has been sent to your email ${emailController.text}."), + const SizedBox( + height: 38, + ), + SizedBox( + width: double.infinity, + child: CustomUI.customElevatedButton(() { + context.pop(); + }, 'Back to Login'), + ), + ], + ); + } + return Column( + children: [ + CustomUI.customTextField( + emailController, 'Email', false, (value) { + if (value == null || value.isEmpty) { + return 'Please enter email.'; + } else if (!value.contains('@')) { + return 'Please enter a valid email.'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 38, + ), + SizedBox( + width: double.infinity, + child: CustomUI.customElevatedButton(() { + if (formKey.currentState!.validate()) { + locator().add( + ResetPasswordButtonPressed( + email: emailController.text, + ), + ); + } + }, 'Reset Password'), + ), + const SizedBox( + height: 26, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Don\'t want to reset anymore?', + style: AppTextStyles.s14( + color: AppColor.appSecondaryBlack, + fontType: FontType.REGULAR, + ), + ), + TextButton( + onPressed: () { + context.pop(); + }, + style: ButtonStyle( + overlayColor: WidgetStateProperty.all( + Colors.transparent, + ), + ), + child: Text( + 'Go back', + style: AppTextStyles.s14( + color: AppColor.appPrimary, + fontType: FontType.MEDIUM, + ), + ), + ), + ], + ), + ], + ); + }, + ), + ), + ), + ), + ], + ), + ), + )); } } diff --git a/lib/presentation/authentication/mobile/sign_up_view_mobile.dart b/lib/presentation/authentication/mobile/sign_up_view_mobile.dart index 848ceaf..ec85c3a 100644 --- a/lib/presentation/authentication/mobile/sign_up_view_mobile.dart +++ b/lib/presentation/authentication/mobile/sign_up_view_mobile.dart @@ -1,14 +1,288 @@ +import 'dart:io'; + +import 'package:expandable_page_view/expandable_page_view.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:monumento/application/authentication/login_register/login_register_bloc.dart'; +import 'package:monumento/presentation/authentication/desktop/widgets/sign_up_decider_widget.dart'; +import 'package:monumento/service_locator.dart'; +import 'package:monumento/utils/app_colors.dart'; +import 'package:monumento/utils/app_text_styles.dart'; +import 'package:monumento/utils/constants.dart'; -class SignUpViewMobile extends StatelessWidget { +class SignUpViewMobile extends StatefulWidget { const SignUpViewMobile({super.key}); + @override + State createState() => _SignUpViewMobileState(); +} + +class _SignUpViewMobileState extends State { + late TextEditingController emailController; + late TextEditingController passwordController; + late TextEditingController nameController; + late TextEditingController usernameController; + late TextEditingController statusController; + late PageController controller; + GlobalKey formKey = GlobalKey(); + XFile? image; + bool render = false; + + @override + void initState() { + emailController = TextEditingController(); + passwordController = TextEditingController(); + nameController = TextEditingController(); + usernameController = TextEditingController(); + statusController = TextEditingController(); + controller = PageController(); + super.initState(); + } + + @override + void dispose() { + emailController.dispose(); + passwordController.dispose(); + nameController.dispose(); + usernameController.dispose(); + statusController.dispose(); + controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Scaffold( - backgroundColor: Colors.white, - body: Center( - child: Text('Sign Up View Mobile'), + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: AppColor.appBackground, + body: Form( + key: formKey, + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(), + render == false + ? Image.asset( + 'assets/logo_auth.png', + height: 105, + width: 136, + ) + : SvgPicture.asset( + 'assets/desktop/logo_desktop.svg', + height: 25, + width: 161, + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 50), + width: 380, + child: BlocListener( + bloc: locator(), + listener: (context, state) { + if (state is SignUpFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + ), + ); + } + if (state is SigninWithGoogleFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + ), + ); + } + }, + child: BlocBuilder( + bloc: locator(), + builder: (context, state) { + if (state is LoginRegisterLoading) { + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + AppColor.appPrimary, + ), + ), + ); + } + if (state is SignUpSuccess) { + context.go('/'); + } + return ExpandablePageView( + pageSnapping: false, + physics: const NeverScrollableScrollPhysics(), + controller: controller, + children: [ + SignUpDeciderWidget( + onSignUpWithEmailPressed: () { + render = true; + setState(() {}); + controller.nextPage( + duration: const Duration(milliseconds: 200), + curve: Curves.easeIn, + ); + }, + onSignUpWithGooglePressed: () { + locator().add( + LoginWithGooglePressed(), + ); + }, + ), + Column( + children: [ + InkWell( + onTap: () async { + final ImagePicker picker = ImagePicker(); + final img = await picker.pickImage( + source: ImageSource.gallery, + ); + setState(() { + image = img; + }); + }, + child: image != null + ? CircleAvatar( + radius: 40, + backgroundImage: + FileImage(File(image!.path))) + : CircleAvatar( + radius: 40, + backgroundColor: AppColor.appGreyAccent, + child: SvgPicture.asset( + 'assets/icons/ic_user.svg', + ), + ), + ), + const SizedBox( + height: 22, + ), + CustomUI.customTextField( + nameController, 'Name', false, (value) { + if (value!.isEmpty) { + return 'Name cannot be empty'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 16, + ), + CustomUI.customTextField( + usernameController, 'Username', false, + (value) { + if (value!.isEmpty) { + return 'Username cannot be empty'; + } + return null; + }, null), + const SizedBox( + height: 16, + ), + CustomUI.customTextField( + statusController, 'Status', false, (value) { + if (value!.isEmpty) { + return 'Status cannot be empty'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 16, + ), + CustomUI.customTextField( + emailController, 'Email', false, (value) { + if (value!.isEmpty) { + return 'Email cannot be empty'; + } else if (!value.contains('@')) { + return 'Invalid email'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 16, + ), + CustomUI.customTextField( + passwordController, 'Password', true, + (value) { + if (value!.isEmpty) { + return 'Password cannot be empty'; + } else if (value.length < 6) { + return 'Password must be at least 6 characters'; + } + return null; + }, AutovalidateMode.onUserInteraction), + const SizedBox( + height: 48, + ), + SizedBox( + width: double.infinity, + child: CustomUI.customElevatedButton(() { + if (formKey.currentState!.validate()) { + locator().add( + SignUpWithEmailPressed( + email: emailController.text, + password: passwordController.text, + name: nameController.text, + username: usernameController.text, + status: statusController.text, + profilePictureFile: image != null + ? File(image!.path) + : null, + ), + ); + } + }, 'Sign Up'), + ), + const SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Already have an account?', + style: AppTextStyles.s14( + color: AppColor.appSecondaryBlack, + fontType: FontType.REGULAR, + ), + ), + TextButton( + onPressed: () { + context.pop(); + }, + style: ButtonStyle( + overlayColor: WidgetStateProperty.all( + Colors.transparent, + ), + ), + child: Text( + 'Login', + style: AppTextStyles.s14( + color: AppColor.appPrimary, + fontType: FontType.MEDIUM, + ), + ), + ), + ], + ), + ], + ), + ], + ); + }, + ), + ), + ), + const Spacer(), + ], + ), + ), ), ); } diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 5c32dfa..2498561 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -1,2 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:monumento/utils/app_colors.dart'; +import 'package:monumento/utils/app_text_styles.dart'; + const defaultProfilePicture = "https://firebasestorage.googleapis.com/v0/b/monumento-fd184.appspot.com/o/profilePictures%2F6415362_account_avatar_profile_user_icon%20(1).png?alt=media&token=3d1a3d55-b631-466a-b12b-f0cc3f3b376d"; + +class CustomUI { + static customTextField( + TextEditingController controller, + String text, + bool toHide, + String? Function(String?)? validateFunction, + AutovalidateMode? autoValid) { + return TextFormField( + controller: controller, + obscureText: toHide, + validator: validateFunction, + autovalidateMode: autoValid, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 15, horizontal: 10), + labelText: text, + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: AppColor.appSecondary, + ), + ), + floatingLabelStyle: AppTextStyles.s14( + color: AppColor.appSecondary, + fontType: FontType.MEDIUM, + ), + border: const OutlineInputBorder( + borderSide: BorderSide( + color: AppColor.appSecondaryBlack, + ), + ), + ), + ); + } + + static customElevatedButton(void Function() onPressed, String text) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + backgroundColor: AppColor.appPrimary, + padding: const EdgeInsets.symmetric(vertical: 15), + ), + onPressed: onPressed, + child: Text( + text, + style: AppTextStyles.s14( + color: AppColor.appSecondary, + fontType: FontType.MEDIUM, + ), + ), + ); + } +}