diff --git a/assets/images/login_background.svg b/assets/images/login_background.svg new file mode 100644 index 0000000..9c34963 --- /dev/null +++ b/assets/images/login_background.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/screens/auth/views/login_screen.dart b/lib/screens/auth/views/login_screen.dart index 104b4e8..1e5a08f 100644 --- a/lib/screens/auth/views/login_screen.dart +++ b/lib/screens/auth/views/login_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:shop/common/app_logger.dart'; import 'package:shop/constants.dart'; import 'package:shop/repository/auth_repository.dart'; @@ -104,79 +105,192 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { - final Size size = MediaQuery.of(context).size; + final theme = Theme.of(context); + const Color brandPrimary = cellphoneZRed; + final Color inputFillColor = + theme.inputDecorationTheme.fillColor ?? lightGreyColor; + + final InputDecorationThemeData pillInputTheme = + theme.inputDecorationTheme.copyWith( + filled: true, + fillColor: inputFillColor, + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32), + borderSide: BorderSide.none, + ), + hintStyle: theme.textTheme.bodyMedium?.copyWith( + color: + theme.textTheme.bodyMedium?.color?.withOpacity(0.6) ?? blackColor40, + fontWeight: FontWeight.w500, + ), + ); return Scaffold( - body: SingleChildScrollView( - child: Column( - children: [ - Image.asset( - "assets/images/login_dark.png", + body: Stack( + children: [ + Positioned.fill( + child: SvgPicture.asset( + 'assets/images/login_background.svg', fit: BoxFit.cover, ), - Padding( - padding: const EdgeInsets.all(defaultPadding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Welcome back!", - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: defaultPadding / 2), - const Text( - "Log in with your data that you intered during your registration.", - ), - const SizedBox(height: defaultPadding), - LogInForm( - formKey: _formKey, - onEmailSaved: (email) => _email = email, - onPasswordSaved: (password) => _password = password, - ), - Align( - child: TextButton( - child: const Text("Forgot password"), - onPressed: () { - Navigator.pushNamed( - context, passwordRecoveryScreenRoute); - }, + ), + SafeArea( + child: Align( + alignment: Alignment.center, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: defaultPadding * 1.4, + vertical: defaultPadding * 1.2, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: defaultPadding * 1.5), + _buildLoginCard( + context: context, + decorationTheme: pillInputTheme, + buttonColor: brandPrimary, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildLoginCard({ + required BuildContext context, + required InputDecorationThemeData decorationTheme, + required Color buttonColor, + }) { + final theme = Theme.of(context); + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(defaultPadding * 1.5), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(36), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Cellphone", + style: (theme.textTheme.headlineLarge ?? + theme.textTheme.headlineSmall ?? + const TextStyle()) + .copyWith( + fontWeight: FontWeight.w800, + color: buttonColor, + letterSpacing: -0.5, + ), + ), + const SizedBox(width: 8), + SvgPicture.asset( + 'assets/logo/CellphoneZ.svg', + height: 48, + width: 48, + ), + ], + ), + const SizedBox(height: defaultPadding * 1.5), + Theme( + data: theme.copyWith( + inputDecorationTheme: decorationTheme, + textSelectionTheme: TextSelectionThemeData( + cursorColor: buttonColor, + selectionColor: buttonColor.withOpacity(0.2), + selectionHandleColor: buttonColor, + ), + ), + child: LogInForm( + formKey: _formKey, + onEmailSaved: (email) => _email = email, + onPasswordSaved: (password) => _password = password, + ), + ), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + Navigator.pushNamed(context, passwordRecoveryScreenRoute); + }, + style: TextButton.styleFrom( + foregroundColor: buttonColor, + ), + child: const Text("Forgot password?"), + ), + ), + const SizedBox(height: defaultPadding), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _handleLogin, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 18), + backgroundColor: buttonColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(32), + ), + elevation: 8, + shadowColor: buttonColor.withOpacity(0.4), + ), + child: _isLoading + ? const SizedBox( + height: 22, + width: 22, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + "Login", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 8), + Icon(Icons.arrow_forward_rounded), + ], ), - ), - SizedBox( - height: - size.height > 700 ? size.height * 0.1 : defaultPadding, - ), - ElevatedButton( - onPressed: _isLoading ? null : _handleLogin, - child: _isLoading - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: - AlwaysStoppedAnimation(Colors.white), - ), - ) - : const Text("Log in"), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Don't have an account?"), - TextButton( - onPressed: () { - Navigator.pushNamed(context, signUpScreenRoute); - }, - child: const Text("Sign up"), - ) - ], - ), - ], + ), + ), + const SizedBox(height: defaultPadding / 2), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Don't have an account?", + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.textTheme.bodyMedium?.color?.withOpacity(0.6) ?? + blackColor60, + ), + ), + TextButton( + onPressed: () { + Navigator.pushNamed(context, signUpScreenRoute); + }, + style: TextButton.styleFrom( + foregroundColor: buttonColor, + ), + child: const Text("Sign up"), ), - ) - ], - ), + ], + ), + ], ), ); } diff --git a/lib/screens/auth/views/password_recovery_screen.dart b/lib/screens/auth/views/password_recovery_screen.dart index ee381d9..942a6ea 100644 --- a/lib/screens/auth/views/password_recovery_screen.dart +++ b/lib/screens/auth/views/password_recovery_screen.dart @@ -1,18 +1,252 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shop/constants.dart'; +import 'package:shop/repository/auth_repository.dart'; -import '../../../components/buy_full_ui_kit.dart'; - -class PasswordRecoveryScreen extends StatelessWidget { +class PasswordRecoveryScreen extends StatefulWidget { const PasswordRecoveryScreen({super.key}); + @override + State createState() => _PasswordRecoveryScreenState(); +} + +class _PasswordRecoveryScreenState extends State { + final GlobalKey _formKey = GlobalKey(); + final AuthRepository _authRepository = AuthRepository(); + + String? _email; + bool _isSubmitting = false; + @override Widget build(BuildContext context) { - return const BuyFullKit(images: [ - "assets/screens/Forgot_password.png", - "assets/screens/Forgot password 6.png", - "assets/screens/Enter verification code.png", - "assets/screens/Verificaition code.png", - "assets/screens/Reset password.png", - ]); + final theme = Theme.of(context); + const Color brandPrimary = cellphoneZRed; + final Color inputFillColor = + theme.inputDecorationTheme.fillColor ?? lightGreyColor; + + final InputDecorationThemeData pillInputTheme = + theme.inputDecorationTheme.copyWith( + filled: true, + fillColor: inputFillColor, + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32), + borderSide: BorderSide.none, + ), + hintStyle: theme.textTheme.bodyMedium?.copyWith( + color: + theme.textTheme.bodyMedium?.color?.withOpacity(0.6) ?? blackColor40, + fontWeight: FontWeight.w500, + ), + ); + + return Scaffold( + body: Stack( + children: [ + Positioned.fill( + child: SvgPicture.asset( + 'assets/images/login_background.svg', + fit: BoxFit.cover, + ), + ), + SafeArea( + child: Align( + alignment: Alignment.center, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: defaultPadding * 1.4, + vertical: defaultPadding * 1.2, + ), + child: _buildRecoveryCard( + context: context, + decorationTheme: pillInputTheme, + buttonColor: brandPrimary, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildRecoveryCard({ + required BuildContext context, + required InputDecorationThemeData decorationTheme, + required Color buttonColor, + }) { + final theme = Theme.of(context); + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(defaultPadding * 1.5), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(36), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Cellphone", + style: (theme.textTheme.headlineLarge ?? + theme.textTheme.headlineSmall ?? + const TextStyle()) + .copyWith( + fontWeight: FontWeight.w800, + color: buttonColor, + letterSpacing: -0.5, + ), + ), + const SizedBox(width: 8), + SvgPicture.asset( + 'assets/logo/CellphoneZ.svg', + height: 48, + width: 48, + ), + ], + ), + const SizedBox(height: defaultPadding * 1.2), + Text( + "Forgot password", + style: (theme.textTheme.headlineSmall ?? + theme.textTheme.headlineMedium ?? + const TextStyle()) + .copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + ), + ), + const SizedBox(height: defaultPadding * 0.5), + Text( + "Enter the email address associated with your account so we can send you a reset link.", + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7) ?? + blackColor60, + ), + ), + const SizedBox(height: defaultPadding * 1.5), + Theme( + data: theme.copyWith( + inputDecorationTheme: decorationTheme, + textSelectionTheme: TextSelectionThemeData( + cursorColor: buttonColor, + selectionColor: buttonColor.withOpacity(0.2), + selectionHandleColor: buttonColor, + ), + ), + child: Form( + key: _formKey, + child: TextFormField( + onSaved: (email) => _email = email?.trim(), + validator: emaildValidator.call, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + hintText: "Email address", + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + vertical: defaultPadding * 0.75, + ), + child: SvgPicture.asset( + "assets/icons/Message.svg", + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + theme.textTheme.bodyLarge?.color?.withOpacity(0.3) ?? + blackColor40, + BlendMode.srcIn, + ), + ), + ), + ), + ), + ), + ), + const SizedBox(height: defaultPadding * 1.5), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isSubmitting ? null : _handleSubmit, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 18), + backgroundColor: buttonColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(32), + ), + elevation: 8, + shadowColor: buttonColor.withOpacity(0.4), + ), + child: _isSubmitting + ? const SizedBox( + height: 22, + width: 22, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text( + "Send reset link", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + const SizedBox(height: defaultPadding), + Center( + child: TextButton( + onPressed: () { + Navigator.pop(context); + }, + style: TextButton.styleFrom( + foregroundColor: buttonColor, + ), + child: const Text("Back to login"), + ), + ), + ], + ), + ); + } + + Future _handleSubmit() async { + if (!_formKey.currentState!.validate()) return; + _formKey.currentState!.save(); + if (_email == null) return; + + setState(() { + _isSubmitting = true; + }); + + try { + await _authRepository.resetPassword(_email!); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Reset link sent! Please check $_email to continue the process.'), + ), + ); + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Unable to send reset link: ${e.toString()}'), + backgroundColor: Colors.red, + ), + ); + } finally { + if (mounted) { + setState(() { + _isSubmitting = false; + }); + } + } } } diff --git a/lib/screens/auth/views/signup_screen.dart b/lib/screens/auth/views/signup_screen.dart index f93c538..e72821b 100644 --- a/lib/screens/auth/views/signup_screen.dart +++ b/lib/screens/auth/views/signup_screen.dart @@ -1,9 +1,8 @@ -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:shop/screens/auth/views/components/sign_up_form.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shop/constants.dart'; import 'package:shop/route/route_constants.dart'; - -import '../../../constants.dart'; +import 'package:shop/screens/auth/views/components/sign_up_form.dart'; class SignUpScreen extends StatefulWidget { const SignUpScreen({super.key}); @@ -13,96 +12,247 @@ class SignUpScreen extends StatefulWidget { } class _SignUpScreenState extends State { - final _formKey = GlobalKey(); + final GlobalKey _formKey = GlobalKey(); + bool _agreedToTerms = false; @override Widget build(BuildContext context) { + final theme = Theme.of(context); + const Color brandPrimary = cellphoneZRed; + final Color inputFillColor = + theme.inputDecorationTheme.fillColor ?? lightGreyColor; + + final InputDecorationThemeData pillInputTheme = + theme.inputDecorationTheme.copyWith( + filled: true, + fillColor: inputFillColor, + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32), + borderSide: BorderSide.none, + ), + hintStyle: theme.textTheme.bodyMedium?.copyWith( + color: + theme.textTheme.bodyMedium?.color?.withOpacity(0.6) ?? blackColor40, + fontWeight: FontWeight.w500, + ), + ); + return Scaffold( - body: SingleChildScrollView( - child: Column( - children: [ - Image.asset( - "assets/images/signUp_dark.png", - height: MediaQuery.of(context).size.height * 0.35, - width: double.infinity, + body: Stack( + children: [ + Positioned.fill( + child: SvgPicture.asset( + 'assets/images/login_background.svg', fit: BoxFit.cover, ), - Padding( - padding: const EdgeInsets.all(defaultPadding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Let’s get started!", - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: defaultPadding / 2), - const Text( - "Please enter your valid data in order to create an account.", - ), - const SizedBox(height: defaultPadding), - SignUpForm(formKey: _formKey), - const SizedBox(height: defaultPadding), - Row( - children: [ - Checkbox( - onChanged: (value) {}, - value: false, - ), - Expanded( - child: Text.rich( - TextSpan( - text: "I agree with the", - children: [ - TextSpan( - recognizer: TapGestureRecognizer() - ..onTap = () { - Navigator.pushNamed( - context, termsOfServicesScreenRoute); - }, - text: " Terms of service ", - style: const TextStyle( - color: primaryColor, - fontWeight: FontWeight.w500, - ), - ), - const TextSpan( - text: "& privacy policy.", - ), - ], - ), + ), + SafeArea( + child: Align( + alignment: Alignment.center, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: defaultPadding * 1.4, + vertical: defaultPadding * 1.2, + ), + child: _buildSignUpCard( + context: context, + decorationTheme: pillInputTheme, + buttonColor: brandPrimary, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildSignUpCard({ + required BuildContext context, + required InputDecorationThemeData decorationTheme, + required Color buttonColor, + }) { + final theme = Theme.of(context); + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(defaultPadding * 1.5), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(36), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Cellphone", + style: (theme.textTheme.headlineLarge ?? + theme.textTheme.headlineSmall ?? + const TextStyle()) + .copyWith( + fontWeight: FontWeight.w800, + color: buttonColor, + letterSpacing: -0.5, + ), + ), + const SizedBox(width: 8), + SvgPicture.asset( + 'assets/logo/CellphoneZ.svg', + height: 48, + width: 48, + ), + ], + ), + const SizedBox(height: defaultPadding * 1.2), + Text( + "Create account", + style: (theme.textTheme.headlineSmall ?? + theme.textTheme.headlineMedium ?? + const TextStyle()) + .copyWith( + fontWeight: FontWeight.w700, + color: theme.colorScheme.onSurface, + ), + ), + const SizedBox(height: defaultPadding * 0.5), + Text( + "Please enter your valid data in order to create an account.", + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7) ?? + blackColor60, + ), + ), + const SizedBox(height: defaultPadding * 1.5), + Theme( + data: theme.copyWith( + inputDecorationTheme: decorationTheme, + textSelectionTheme: TextSelectionThemeData( + cursorColor: buttonColor, + selectionColor: buttonColor.withOpacity(0.2), + selectionHandleColor: buttonColor, + ), + ), + child: SignUpForm(formKey: _formKey), + ), + const SizedBox(height: defaultPadding), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Checkbox( + value: _agreedToTerms, + activeColor: buttonColor, + onChanged: (value) { + setState(() { + _agreedToTerms = value ?? false; + }); + }, + ), + const SizedBox(width: 8), + Expanded( + child: Wrap( + spacing: 4, + runSpacing: 4, + children: [ + Text( + "I agree with the", + style: theme.textTheme.bodyMedium, + ), + GestureDetector( + onTap: () { + Navigator.pushNamed( + context, + termsOfServicesScreenRoute, + ); + }, + child: Text( + "Terms of service", + style: theme.textTheme.bodyMedium?.copyWith( + color: buttonColor, + fontWeight: FontWeight.w600, ), - ) - ], - ), - const SizedBox(height: defaultPadding * 2), - ElevatedButton( - onPressed: () { - // There is 2 more screens while user complete their profile - // afre sign up, it's available on the pro version get it now - // 🔗 https://theflutterway.gumroad.com/l/fluttershop - Navigator.pushNamed(context, entryPointScreenRoute); - }, - child: const Text("Continue"), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Do you have an account?"), - TextButton( - onPressed: () { - Navigator.pushNamed(context, logInScreenRoute); - }, - child: const Text("Log in"), - ) - ], - ), - ], + ), + ), + Text( + "& privacy policy.", + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.textTheme.bodyMedium?.color + ?.withOpacity(0.7) ?? + blackColor60, + ), + ), + ], + ), ), - ) - ], - ), + ], + ), + const SizedBox(height: defaultPadding * 1.5), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _handleContinue, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 18), + backgroundColor: buttonColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(32), + ), + elevation: 8, + shadowColor: buttonColor.withOpacity(0.4), + ), + child: const Text( + "Continue", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + const SizedBox(height: defaultPadding), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Do you have an account?", + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7) ?? + blackColor60, + ), + ), + TextButton( + onPressed: () { + Navigator.pushNamed(context, logInScreenRoute); + }, + style: TextButton.styleFrom( + foregroundColor: buttonColor, + ), + child: const Text("Log in"), + ) + ], + ), + ], ), ); } + + void _handleContinue() { + if (!_formKey.currentState!.validate()) return; + if (!_agreedToTerms) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Please agree to the Terms of service & privacy policy to continue.', + ), + ), + ); + return; + } + + _formKey.currentState!.save(); + Navigator.pushNamed(context, entryPointScreenRoute); + } }