feat: Update authentication flow and UI enhancements for sign-in and forgot password screens

This commit is contained in:
vergiLgood1 2025-05-27 04:34:04 +07:00
parent 0da8d5621c
commit 1967fdb6b8
11 changed files with 402 additions and 182 deletions

View File

@ -116,7 +116,7 @@ class AuthenticationRepository extends GetxController {
}
} catch (e) {
Logger().e('Error in screenRedirect: $e');
_navigateToRoute(AppRoutes.checkLocation);
_navigateToRoute(AppRoutes.signIn);
} finally {
_isRedirecting = false;
Logger().d('Screen redirect completed');

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sigap/src/features/auth/data/repositories/authentication_repository.dart';
import 'package:sigap/src/shared/widgets/state_screeen/state_screen.dart';
import 'package:sigap/src/utils/constants/image_strings.dart';
import 'package:sigap/src/utils/popups/loaders.dart';
import 'package:sigap/src/utils/validators/validation.dart';
@ -45,9 +47,6 @@ class ForgotPasswordController extends GetxController {
try {
isLoading.value = true;
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
await AuthenticationRepository.instance.sendResetPasswordForEmail(
emailController.text,
);
@ -58,6 +57,19 @@ class ForgotPasswordController extends GetxController {
title: 'Success',
message: 'Reset password email sent successfully.',
);
Get.off(
() => StateScreen(
title: 'Check Your Email',
subtitle: 'Please check your email for the reset link.',
image: TImages.customerSupport,
isSvg: true,
primaryButtonTitle: 'Go Back',
showButton: true,
onPressed: () => Get.back(),
),
);
} catch (e) {
Get.snackbar(
'Error',

View File

@ -64,17 +64,16 @@ class SignInController extends GetxController {
isLoading.value = true;
// Attempt to sign in
final signInResult = await _authRepo.loginWithEmailPassword(
await _authRepo.loginWithEmailPassword(
email: email.text.trim(),
password: password.text.trim(),
);
// Handle result
_logger.i('Sign in successful: $signInResult');
// _logger.i('Sign in successful: $signInResult');
// Redirect based on user's profile status
_authRepo.screenRedirect();
} catch (e) {
isLoading.value = false;
@ -106,7 +105,6 @@ class SignInController extends GetxController {
// Redirect based on user's profile status
_authRepo.screenRedirect();
} catch (e) {
isLoading.value = false;
@ -121,4 +119,79 @@ class SignInController extends GetxController {
isLoading.value = false;
}
}
// Sign in with github
Future<void> githubSignIn() async {
try {
isLoading.value = true;
// Attempt to sign in with GitHub
await _authRepo.signInWithGithub();
// Redirect based on user's profile status
_authRepo.screenRedirect();
} catch (e) {
isLoading.value = false;
// Show error
TLoaders.errorSnackBar(
title: 'GitHub Sign In Failed',
message: e.toString(),
);
_logger.e('GitHub sign in error: $e');
} finally {
isLoading.value = false;
}
}
// Sign in with Facebook
Future<void> facebookSignIn() async {
try {
isLoading.value = true;
// Attempt to sign in with Facebook
await _authRepo.signInWithFacebook();
// Redirect based on user's profile status
_authRepo.screenRedirect();
} catch (e) {
isLoading.value = false;
// Show error
TLoaders.errorSnackBar(
title: 'Facebook Sign In Failed',
message: e.toString(),
);
_logger.e('Facebook sign in error: $e');
} finally {
isLoading.value = false;
}
}
// Sign in with Apple
Future<void> appleSignIn() async {
try {
isLoading.value = true;
// Attempt to sign in with Apple
await _authRepo.signInWithApple();
// Redirect based on user's profile status
_authRepo.screenRedirect();
} catch (e) {
isLoading.value = false;
// Show error
TLoaders.errorSnackBar(
title: 'Apple Sign In Failed',
message: e.toString(),
);
_logger.e('Apple sign in error: $e');
} finally {
isLoading.value = false;
}
}
}

View File

@ -1,11 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:sigap/src/features/auth/presentasion/controllers/forgot-password/forgot_password_controller.dart';
import 'package:sigap/src/features/auth/presentasion/widgets/auth_button.dart';
import 'package:sigap/src/features/auth/presentasion/widgets/auth_header.dart';
import 'package:sigap/src/shared/widgets/text/custom_text_field.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/constants/image_strings.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
import 'package:sigap/src/utils/helpers/helper_functions.dart';
class ForgotPasswordScreen extends StatelessWidget {
const ForgotPasswordScreen({super.key});
@ -15,6 +19,8 @@ class ForgotPasswordScreen extends StatelessWidget {
// Get the controller
final controller = Get.find<ForgotPasswordController>();
final isDarkMode = THelperFunctions.isDarkMode(context);
// Set system overlay style
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
@ -24,26 +30,29 @@ class ForgotPasswordScreen extends StatelessWidget {
);
return Scaffold(
backgroundColor: TColors.light,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: TColors.textPrimary),
icon: Icon(
Icons.arrow_back,
color: isDarkMode ? TColors.accent : TColors.primary,
),
onPressed: controller.goBack,
),
),
body: SafeArea(
child: Center(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
padding: const EdgeInsets.all(TSizes.defaultSpace),
child: Obx(
() => Form(
key: controller.formKey,
child:
controller.isEmailSent.value
? _buildSuccessView(controller)
: _buildFormView(controller),
: _buildFormView(controller, context),
),
),
),
),
@ -52,16 +61,28 @@ class ForgotPasswordScreen extends StatelessWidget {
);
}
Widget _buildFormView(ForgotPasswordController controller) {
Widget _buildFormView(
ForgotPasswordController controller,
BuildContext? context,
) {
final isDark = THelperFunctions.isDarkMode(context!);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Header
// Logo centered
SvgPicture.asset(TImages.lightAppBgLogo, height: 100, width: 100),
const SizedBox(height: 16),
// Header centered
const AuthHeader(
crossAxisAlignment: CrossAxisAlignment.center,
title: 'Forgot Password',
subtitle: 'Enter your email to reset your password',
),
const SizedBox(height: 24),
// Email field
Obx(
() => CustomTextField(
@ -70,6 +91,12 @@ class ForgotPasswordScreen extends StatelessWidget {
validator: controller.validateEmail,
keyboardType: TextInputType.emailAddress,
errorText: controller.emailError.value,
hintText: 'enter your email',
prefixIcon: Icon(
Icons.email_outlined,
size: 20,
color: isDark ? TColors.accent : TColors.primary,
),
),
),
@ -89,8 +116,11 @@ class ForgotPasswordScreen extends StatelessWidget {
Widget _buildSuccessView(ForgotPasswordController controller) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Logo centered
SvgPicture.asset(TImages.lightAppBgLogo, height: 100, width: 100),
const SizedBox(height: 32),
// Success icon

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_tabler_icons/flutter_tabler_icons.dart';
import 'package:get/get.dart';
import 'package:sigap/src/features/auth/presentasion/controllers/signin/signin_controller.dart';
@ -8,6 +9,7 @@ import 'package:sigap/src/features/auth/presentasion/widgets/auth_header.dart';
import 'package:sigap/src/features/auth/presentasion/widgets/password_field.dart';
import 'package:sigap/src/features/auth/presentasion/widgets/social_button.dart';
import 'package:sigap/src/shared/widgets/text/custom_text_field.dart';
import 'package:sigap/src/utils/constants/image_strings.dart'; // Added for logo image
import 'package:sigap/src/utils/helpers/helper_functions.dart';
import 'package:sigap/src/utils/validators/validation.dart';
@ -26,25 +28,35 @@ class SignInScreen extends StatelessWidget {
final isDarkMode = THelperFunctions.isDarkMode(context);
return Scaffold(
// Use dynamic background color from theme
body: SafeArea(
child: Center(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Logo centered at the top
SvgPicture.asset(
TImages.lightAppBgLogo,
height: 100,
width: 100,
),
const SizedBox(height: 16),
// Header - pass isDarkMode to AuthHeader if needed
// Header centered
const AuthHeader(
crossAxisAlignment: CrossAxisAlignment.center,
title: 'Welcome Back',
subtitle: 'Sign in to your account to continue',
),
// Email field
const SizedBox(height: 24),
// Email field with icon
Obx(
() => CustomTextField(
label: 'Email',
@ -53,10 +65,11 @@ class SignInScreen extends StatelessWidget {
keyboardType: TextInputType.emailAddress,
errorText: controller.emailError.value,
textInputAction: TextInputAction.next,
prefixIcon: const Icon(TablerIcons.mail, size: 20),
),
),
// Password field
// Password field with icon
Obx(
() => PasswordField(
label: 'Password',
@ -65,6 +78,7 @@ class SignInScreen extends StatelessWidget {
isVisible: controller.isPasswordVisible,
errorText: controller.passwordError.value,
onToggleVisibility: controller.togglePasswordVisibility,
prefixIcon: const Icon(TablerIcons.lock, size: 20),
),
),
@ -101,16 +115,60 @@ class SignInScreen extends StatelessWidget {
const SizedBox(height: 24),
// Social sign in buttons
SocialButton(
text: 'Continue with Google',
icon: Icon(
// Social sign in buttons - Row of social icons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Google Sign In
Expanded(
child: SocialButton(
shapeType: SocialButtonShapeType.circular,
icon: const Icon(
TablerIcons.brand_google,
color: Colors.white,
size: 20,
),
backgroundColor: const Color(0xFFDB4437),
foregroundColor: Colors.white,
onPressed: () => controller.googleSignIn(),
),
),
const SizedBox(width: 0),
// GitHub Sign In
Expanded(
child: SocialButton(
shapeType: SocialButtonShapeType.circular,
icon: const Icon(
TablerIcons.brand_apple,
color: Colors.white,
size: 20,
),
backgroundColor: const Color(0xFF333333),
foregroundColor: Colors.white,
onPressed: () => controller.appleSignIn(),
),
),
const SizedBox(width: 0),
// Facebook Sign In
Expanded(
child: SocialButton(
shapeType: SocialButtonShapeType.circular,
icon: const Icon(
TablerIcons.brand_facebook,
color: Colors.white,
size: 20,
),
backgroundColor: const Color(0xFF1877F2),
foregroundColor: Colors.white,
onPressed: () => controller.facebookSignIn(),
),
),
],
),
const SizedBox(height: 16),
@ -141,6 +199,7 @@ class SignInScreen extends StatelessWidget {
),
),
),
),
);
}
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
import 'package:sigap/src/utils/helpers/helper_functions.dart';
class AuthButton extends StatelessWidget {
final String text;
@ -19,6 +21,7 @@ class AuthButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = THelperFunctions.isDarkMode(context);
return SizedBox(
width: double.infinity,
height: 55,
@ -26,7 +29,8 @@ class AuthButton extends StatelessWidget {
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor ?? Theme.of(context).primaryColor,
foregroundColor: textColor ?? Theme.of(context).colorScheme.onPrimary,
foregroundColor:
textColor ?? (isDark ? TColors.primary : TColors.accent),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(TSizes.buttonRadius),

View File

@ -5,15 +5,21 @@ import 'package:sigap/src/utils/helpers/helper_functions.dart';
class AuthHeader extends StatelessWidget {
final String title;
final String subtitle;
final CrossAxisAlignment crossAxisAlignment;
const AuthHeader({super.key, required this.title, required this.subtitle});
const AuthHeader({
super.key,
required this.title,
required this.subtitle,
this.crossAxisAlignment = CrossAxisAlignment.start,
});
@override
Widget build(BuildContext context) {
final dark = THelperFunctions.isDarkMode(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: crossAxisAlignment,
children: [
Text(
title,

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
enum SocialButtonShapeType { defaultShape, rounded, circular }
class SocialButton extends StatelessWidget {
final String text;
final String? text;
final Icon? icon;
final String? iconImage;
final double? iconImageWidth;
@ -12,10 +14,11 @@ class SocialButton extends StatelessWidget {
final Color? foregroundColor;
final Color? borderColor;
final bool isVisible;
final SocialButtonShapeType shapeType;
const SocialButton({
super.key,
required this.text,
this.text,
this.icon,
this.iconImage,
this.iconImageWidth,
@ -25,53 +28,81 @@ class SocialButton extends StatelessWidget {
this.foregroundColor,
this.borderColor,
this.isVisible = true,
this.shapeType = SocialButtonShapeType.defaultShape,
});
@override
Widget build(BuildContext context) {
if (!isVisible) return const SizedBox.shrink();
return SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: onPressed,
style: OutlinedButton.styleFrom(
backgroundColor: backgroundColor ?? Colors.white,
foregroundColor: foregroundColor ?? Colors.black,
side: BorderSide(color: borderColor ?? Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(TSizes.buttonRadius),
),
padding: const EdgeInsets.symmetric(
OutlinedBorder shape;
EdgeInsetsGeometry padding;
switch (shapeType) {
case SocialButtonShapeType.rounded:
shape = RoundedRectangleBorder(borderRadius: BorderRadius.circular(30));
padding = const EdgeInsets.symmetric(
vertical: TSizes.md,
horizontal: TSizes.md,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 24,
width: 24,
child:
);
break;
case SocialButtonShapeType.circular:
shape = const CircleBorder();
padding = const EdgeInsets.all(0);
break;
default:
shape = RoundedRectangleBorder(
borderRadius: BorderRadius.circular(TSizes.buttonRadius),
);
padding = const EdgeInsets.symmetric(
vertical: TSizes.md,
horizontal: TSizes.md,
);
}
Widget iconWidget =
iconImage == null
? icon
? icon ?? const SizedBox.shrink()
: Image.asset(
iconImage!,
width: iconImageWidth,
height: iconImageHeight,
),
),
);
Widget child;
if (text == null || text!.isEmpty) {
child = iconWidget;
} else {
child = Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 24, width: 24, child: iconWidget),
const SizedBox(width: TSizes.md),
Text(
text,
text!,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: foregroundColor ?? Colors.black,
fontWeight: FontWeight.w500,
),
),
],
);
}
return SizedBox(
width: shapeType == SocialButtonShapeType.circular ? 48 : double.infinity,
height: shapeType == SocialButtonShapeType.circular ? 48 : null,
child: OutlinedButton(
onPressed: onPressed,
style: OutlinedButton.styleFrom(
backgroundColor: backgroundColor ?? Colors.white,
foregroundColor: foregroundColor ?? Colors.black,
side: BorderSide(color: borderColor ?? Colors.grey.shade300),
shape: shape,
padding: padding,
),
child: child,
),
);
}

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:sigap/src/cores/services/location_service.dart';
import 'package:sigap/src/features/onboarding/presentasion/pages/role-selection/role_signup_pageview.dart';
import 'package:sigap/src/utils/constants/image_strings.dart';
@ -11,6 +12,8 @@ class CheckLocationController extends GetxController
final VoidCallback? onSuccess;
final LocationService _locationService = LocationService.instance;
final storage = GetStorage();
// Reactive variables
final RxInt currentPage = 0.obs;
final RxBool isLoading = false.obs;
@ -146,6 +149,8 @@ class CheckLocationController extends GetxController
// Navigate to communication slide before zooming
_navigateToCommunicationSlide();
storage.write('isFirstTime', false);
// Give time to see the success message before animating
await Future.delayed(Duration(milliseconds: 800));
animController

View File

@ -81,16 +81,16 @@ class OnboardingController extends GetxController
}
}
// Method to skip to welcome screen
void skipToWelcomeScreen() {
// Method to skip to check location screen
void skipToCheckLocation() {
skipOnboarding();
}
// Method to navigate to welcome screen
// Method to navigate to check location screen
void skipOnboarding() {
// Mark onboarding as completed in storage
storage.write('isFirstTime', true);
Get.offAllNamed(AppRoutes.welcome);
storage.write('isFirstTime', false);
Get.offAllNamed(AppRoutes.checkLocation);
}
// Method to check location validity and proceed with auth flow
@ -136,7 +136,7 @@ class OnboardingController extends GetxController
}
void goToSignIn() {
storage.write('isFirstTime', true);
storage.write('isFirstTime', false);
Get.offAllNamed(AppRoutes.signIn);
}

View File

@ -47,7 +47,7 @@ class TLoaders {
isDismissible: true,
shouldIconPulse: true,
colorText: Colors.white,
backgroundColor: TColors.primary,
backgroundColor: TColors.success,
snackPosition: SnackPosition.TOP,
duration: Duration(seconds: duration),
margin: const EdgeInsets.all(10),