Merge pull request 'forget_password_design' (#112) from forget_password_design into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/112
This commit is contained in:
a.ghabeli
2025-09-08 10:17:45 +03:30
20 changed files with 1175 additions and 130 deletions
+40
View File
@@ -13,6 +13,9 @@ class AuthViewModel with ChangeNotifier {
: _authRepository = authRepository { : _authRepository = authRepository {
usernameController.addListener(_onTextChanged); usernameController.addListener(_onTextChanged);
passwordController.addListener(_onTextChanged); passwordController.addListener(_onTextChanged);
emailController.addListener(_onTextChanged);
otpController.addListener(_onTextChanged);
newPasswordController.addListener(_onTextChanged);
// usernameFocus.addListener(_onTextChanged); // usernameFocus.addListener(_onTextChanged);
// passwordFocus.addListener(_onTextChanged); // passwordFocus.addListener(_onTextChanged);
} }
@@ -20,12 +23,18 @@ class AuthViewModel with ChangeNotifier {
final TextEditingController usernameController = TextEditingController(); final TextEditingController usernameController = TextEditingController();
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
final TextEditingController emailController = TextEditingController(); final TextEditingController emailController = TextEditingController();
final TextEditingController newPasswordController = TextEditingController();
final TextEditingController otpController = TextEditingController();
final FocusNode usernameFocus = FocusNode(); final FocusNode usernameFocus = FocusNode();
final FocusNode passwordFocus = FocusNode(); final FocusNode passwordFocus = FocusNode();
final FocusNode emailFocus = FocusNode(); final FocusNode emailFocus = FocusNode();
final FocusNode newPasswordFocus = FocusNode();
final FocusNode otpFocus = FocusNode();
bool _obscurePassword = true; bool _obscurePassword = true;
bool get obscurePassword => _obscurePassword; bool get obscurePassword => _obscurePassword;
bool _obscureForgotPassword = true;
bool get obscureForgotPassword => _obscureForgotPassword;
bool get isLoginEnabled => bool get isLoginEnabled =>
usernameController.text.isNotEmpty && passwordController.text.isNotEmpty; usernameController.text.isNotEmpty && passwordController.text.isNotEmpty;
@@ -43,6 +52,37 @@ class AuthViewModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
bool get isEmailValid {
final email = emailController.text.trim();
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
return emailRegex.hasMatch(email);
}
bool get isOtpValid {
final otp = otpController.text.trim();
return otp.length == 5;
}
bool get canSubmitOtpPage {
return isEmailValid && isOtpValid;
}
bool get isNewPasswordValid {
final password = newPasswordController.text.trim();
final hasMinLength = password.length >= 5;
final hasUppercase = RegExp(r'[A-Z]').hasMatch(password);
final hasLowercase = RegExp(r'[a-z]').hasMatch(password);
final hasSpecialChar = RegExp(r'[!@#\$%&\*\^]').hasMatch(password);
return hasMinLength && hasUppercase && hasLowercase && hasSpecialChar;
}
void toggleForgotPasswordVisibility() {
_obscureForgotPassword = !_obscureForgotPassword;
notifyListeners();
}
void _onTextChanged() { void _onTextChanged() {
notifyListeners(); notifyListeners();
} }
@@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../login/widgets/login_textfield.dart';
import '../../shared/base_button.dart';
class DesktopForgotPasswordCreatePage extends StatelessWidget {
const DesktopForgotPasswordCreatePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Center(
child: SizedBox(
width: 364,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
ForgotPasswordCreateStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
ForgotPasswordCreateStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
LoginTextField(
controller: viewModel.newPasswordController,
focusNode: viewModel.newPasswordFocus,
label: ForgotPasswordCreateStrings.createNewPassword,
iconPath: AssetsManager.passwordIcon,
isPassword: true,
obscureText: viewModel.obscureForgotPassword,
onToggleVisibility:
viewModel.toggleForgotPasswordVisibility,
),
SizedBox(height: 16.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLast5Character,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeast1uppercase,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeast1lowercase,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeastSpecial,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 80.0.h()),
viewModel.isLoading
? BaseButton(
isEnabled: false,
text: ForgotPasswordCreateStrings.resetLink,
onPressed: null,
)
: BaseButton(
isEnabled: viewModel.isNewPasswordValid,
text: ForgotPasswordCreateStrings.resetLink,
onPressed:
viewModel.isNewPasswordValid
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const MobileForgotPasswordOtpPage(),
),
);
}
: null,
),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
ForgotPasswordCreateStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
),
),
);
},
),
),
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:tm_app/views/forget_password_create/pages/d_forget_password_create_page.dart';
import 'package:tm_app/views/forget_password_create/pages/m_forget_password_create_page.dart';
import 'package:tm_app/views/forget_password_create/pages/t_forget_password_create_page.dart';
import '../../shared/responsive_builder.dart';
class ForgotPasswordCreateScreen extends StatelessWidget {
const ForgotPasswordCreateScreen({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
mobile: MobileForgotPasswordCreatePage(),
tablet: TabletForgotPasswordCreatePage(),
desktop: DesktopForgotPasswordCreatePage(),
);
}
}
@@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../login/widgets/login_textfield.dart';
import '../../shared/base_button.dart';
class MobileForgotPasswordCreatePage extends StatelessWidget {
const MobileForgotPasswordCreatePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
ForgotPasswordCreateStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
ForgotPasswordCreateStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
LoginTextField(
controller: viewModel.newPasswordController,
focusNode: viewModel.newPasswordFocus,
label: ForgotPasswordCreateStrings.createNewPassword,
iconPath: AssetsManager.passwordIcon,
isPassword: true,
obscureText: viewModel.obscureForgotPassword,
onToggleVisibility:
viewModel.toggleForgotPasswordVisibility,
),
SizedBox(height: 16.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLast5Character,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeast1uppercase,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeast1lowercase,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeastSpecial,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 80.0.h()),
viewModel.isLoading
? BaseButton(
isEnabled: false,
text: ForgotPasswordCreateStrings.resetLink,
onPressed: null,
)
: BaseButton(
isEnabled: viewModel.isNewPasswordValid,
text: ForgotPasswordCreateStrings.resetLink,
onPressed:
viewModel.isNewPasswordValid
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const MobileForgotPasswordOtpPage(),
),
);
}
: null,
),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
ForgotPasswordCreateStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
);
},
),
),
),
);
}
}
@@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../login/widgets/login_textfield.dart';
import '../../shared/base_button.dart';
class TabletForgotPasswordCreatePage extends StatelessWidget {
const TabletForgotPasswordCreatePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Center(
child: SizedBox(
width: 364,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
ForgotPasswordCreateStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
ForgotPasswordCreateStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
LoginTextField(
controller: viewModel.newPasswordController,
focusNode: viewModel.newPasswordFocus,
label: ForgotPasswordCreateStrings.createNewPassword,
iconPath: AssetsManager.passwordIcon,
isPassword: true,
obscureText: viewModel.obscureForgotPassword,
onToggleVisibility:
viewModel.toggleForgotPasswordVisibility,
),
SizedBox(height: 16.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLast5Character,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeast1uppercase,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeast1lowercase,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 6.0.h()),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 16.0.w()),
child: Text(
ForgotPasswordCreateStrings.atLeastSpecial,
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
),
SizedBox(height: 80.0.h()),
viewModel.isLoading
? BaseButton(
isEnabled: false,
text: ForgotPasswordCreateStrings.resetLink,
onPressed: null,
)
: BaseButton(
isEnabled: viewModel.isNewPasswordValid,
text: ForgotPasswordCreateStrings.resetLink,
onPressed:
viewModel.isNewPasswordValid
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const MobileForgotPasswordOtpPage(),
),
);
}
: null,
),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
ForgotPasswordCreateStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
),
),
);
},
),
),
),
);
}
}
@@ -0,0 +1,12 @@
class ForgotPasswordCreateStrings {
ForgotPasswordCreateStrings._();
static const String forgotPasswordTitle = 'Forgot Password';
static const String forgotPasswordSubtitle = 'Change your password';
static const String createNewPassword = 'Create new password';
static const String backToSignInButton = 'Back to Sign In';
static const String resetLink = 'Reset Link';
static const String atLast5Character = '• At least 5 characters';
static const String atLeast1uppercase = '• At least one uppercase letter (AZ)';
static const String atLeast1lowercase = '• At least one lowercase letter (az)';
static const String atLeastSpecial = '• At least one special character: ! @ # \$ % & * ^';
}
@@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:pinput/pinput.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_create/pages/forgot_password_create_screen.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../shared/base_button.dart';
import '../strings/forgot_password_otp_strings.dart';
class DesktopForgotPasswordOtpPage extends StatelessWidget {
const DesktopForgotPasswordOtpPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Center(
child: SizedBox(
width: 364,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
ForgotPasswordOtpStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
ForgotPasswordOtpStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
Pinput(
controller: viewModel.otpController,
length: 5,
pinAnimationType: PinAnimationType.scale,
onCompleted: (value) {
debugPrint('OTP: $value');
},
defaultPinTheme: PinTheme(
width: 50.0.w(),
height: 50.0.h(),
textStyle: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.bold,
),
decoration: BoxDecoration(
color: AppColors.grey0,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.grey40),
),
),
),
SizedBox(height: 24.0.h()),
viewModel.isLoading
? BaseButton(
isEnabled: false,
text: ForgotPasswordOtpStrings.resetLink,
onPressed: null,
)
: BaseButton(
isEnabled:
viewModel.canSubmitOtpPage, // 👈 شرط جدید
text: ForgotPasswordOtpStrings.resetLink,
onPressed:
viewModel.canSubmitOtpPage
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const ForgotPasswordCreateScreen(),
),
);
}
: null,
),
SizedBox(height: 12.0),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
ForgotPasswordOtpStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
),
),
);
},
),
),
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/d_forget_password_otp.dart_page.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/t_forget_password_otp.dart_page.dart';
import '../../shared/responsive_builder.dart';
class ForgotPasswordOtpScreen extends StatelessWidget {
const ForgotPasswordOtpScreen({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
mobile: MobileForgotPasswordOtpPage(),
tablet: TabletForgotPasswordOtpPage(),
desktop: DesktopForgotPasswordOtpPage(),
);
}
}
@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:pinput/pinput.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_create/pages/forgot_password_create_screen.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../shared/base_button.dart';
import '../strings/forgot_password_otp_strings.dart';
class MobileForgotPasswordOtpPage extends StatelessWidget {
const MobileForgotPasswordOtpPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
ForgotPasswordOtpStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
ForgotPasswordOtpStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
Pinput(
controller: viewModel.otpController,
length: 5,
pinAnimationType: PinAnimationType.scale,
onCompleted: (value) {
debugPrint('OTP: $value');
},
defaultPinTheme: PinTheme(
width: 50.0.w(),
height: 50.0.h(),
textStyle: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.bold,
),
decoration: BoxDecoration(
color: AppColors.grey0,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.grey40),
),
),
),
SizedBox(height: 24.0.h()),
viewModel.isLoading
? BaseButton(
isEnabled: false,
text: ForgotPasswordOtpStrings.resetLink,
onPressed: null,
)
: BaseButton(
isEnabled: viewModel.canSubmitOtpPage,
text: ForgotPasswordOtpStrings.resetLink,
onPressed:
viewModel.canSubmitOtpPage
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const ForgotPasswordCreateScreen(),
),
);
}
: null,
),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
ForgotPasswordOtpStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
);
},
),
),
),
);
}
}
@@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:pinput/pinput.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_create/pages/forgot_password_create_screen.dart';
import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../shared/base_button.dart';
import '../strings/forgot_password_otp_strings.dart';
class TabletForgotPasswordOtpPage extends StatelessWidget {
const TabletForgotPasswordOtpPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Center(
child: SizedBox(
width: 364,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
ForgotPasswordOtpStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
ForgotPasswordOtpStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
Pinput(
controller: viewModel.otpController,
length: 5,
pinAnimationType: PinAnimationType.scale,
onCompleted: (value) {
debugPrint('OTP: $value');
},
defaultPinTheme: PinTheme(
width: 50.0.w(),
height: 50.0.h(),
textStyle: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.bold,
),
decoration: BoxDecoration(
color: AppColors.grey0,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.grey40),
),
),
),
SizedBox(height: 24.0.h()),
viewModel.isLoading
? BaseButton(
isEnabled: false,
text: ForgotPasswordOtpStrings.resetLink,
onPressed: null,
)
: BaseButton(
isEnabled:
viewModel.canSubmitOtpPage, // 👈 شرط جدید
text: ForgotPasswordOtpStrings.resetLink,
onPressed:
viewModel.canSubmitOtpPage
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const ForgotPasswordCreateScreen(),
),
);
}
: null,
),
SizedBox(height: 12.0),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
ForgotPasswordOtpStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
),
),
);
},
),
),
),
);
}
}
@@ -0,0 +1,8 @@
class ForgotPasswordOtpStrings {
ForgotPasswordOtpStrings._();
static const String forgotPasswordTitle = 'Forgot Password';
static const String forgotPasswordSubtitle =
'Enter the code sent to your email to reset your password.';
static const String backToSignInButton = 'Back to Sign In';
static const String resetLink = 'Reset Link';
}
@@ -3,6 +3,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/forgot_password_otp_screen.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
@@ -59,37 +60,51 @@ class DesktopForgotPasswordPage extends StatelessWidget {
), ),
), ),
SizedBox(height: 40.0.h()), SizedBox(height: 40.0.h()),
// LoginTextField(
// controller: viewModel.emailController,
// focusNode: viewModel.emailFocus,
// label: ForgotPasswordStrings.usernameHint,
// iconPath: AssetsManager.userIcon,
// isPassword: false,
// ),
// SizedBox(height: 24.0.h()),
LoginTextField( LoginTextField(
controller: viewModel.emailController, controller: viewModel.emailController,
focusNode: viewModel.emailFocus, focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.usernameHint, label: ForgotPasswordStrings.emailHint,
iconPath: AssetsManager.userIcon,
isPassword: false,
),
SizedBox(height: 24.0.h()),
LoginTextField(
controller: viewModel.emailController,
focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.emailAddressHint,
isPassword: false,
),
SizedBox(height: 24.0.h()),
LoginTextField(
controller: viewModel.emailController,
focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.companyIdHint,
isPassword: false, isPassword: false,
), ),
// SizedBox(height: 24.0.h()),
// LoginTextField(
// controller: viewModel.emailController,
// focusNode: viewModel.emailFocus,
// label: ForgotPasswordStrings.companyIdHint,
// isPassword: false,
// ),
SizedBox(height: 70.0.h()), SizedBox(height: 70.0.h()),
BaseButton( viewModel.isLoading
isEnabled: false, ? BaseButton(
text: ForgotPasswordStrings.resetLink, isEnabled: false,
onPressed: () async { text: ForgotPasswordStrings.resetLink,
if (viewModel.isLoginEnabled) { onPressed: null,
await viewModel.login(); )
} : BaseButton(
}, isEnabled: viewModel.isEmailValid,
), text: ForgotPasswordStrings.resetLink,
onPressed:
viewModel.isEmailValid
? () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
const ForgotPasswordOtpScreen(),
),
);
}
: null,
),
SizedBox(height: 12), SizedBox(height: 12),
TextButton( TextButton(
onPressed: () { onPressed: () {
@@ -3,6 +3,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/forgot_password_otp_screen.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
@@ -54,37 +55,51 @@ class MobileForgotPasswordPage extends StatelessWidget {
), ),
), ),
SizedBox(height: 40.0.h()), SizedBox(height: 40.0.h()),
// LoginTextField(
// controller: viewModel.emailController,
// focusNode: viewModel.emailFocus,
// label: ForgotPasswordStrings.usernameHint,
// iconPath: AssetsManager.userIcon,
// isPassword: false,
// ),
// SizedBox(height: 24.0.h()),
LoginTextField( LoginTextField(
controller: viewModel.emailController, controller: viewModel.emailController,
focusNode: viewModel.emailFocus, focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.usernameHint, label: ForgotPasswordStrings.emailHint,
iconPath: AssetsManager.userIcon,
isPassword: false, isPassword: false,
), ),
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
LoginTextField( // LoginTextField(
controller: viewModel.emailController, // controller: viewModel.emailController,
focusNode: viewModel.emailFocus, // focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.emailAddressHint, // label: ForgotPasswordStrings.companyIdHint,
isPassword: false, // isPassword: false,
), // ),
SizedBox(height: 24.0.h()), // SizedBox(height: 70.0.h()),
LoginTextField( viewModel.isLoading
controller: viewModel.emailController, ? BaseButton(
focusNode: viewModel.emailFocus, isEnabled: false,
label: ForgotPasswordStrings.companyIdHint, text: ForgotPasswordStrings.resetLink,
isPassword: false, onPressed: null,
), )
SizedBox(height: 70.0.h()), : BaseButton(
BaseButton( isEnabled: viewModel.isEmailValid,
isEnabled: false, text: ForgotPasswordStrings.resetLink,
text: ForgotPasswordStrings.resetLink, onPressed:
onPressed: () async { viewModel.isEmailValid
if (viewModel.isLoginEnabled) { ? () async {
await viewModel.login(); Navigator.push(
} context,
}, MaterialPageRoute(
), builder:
(context) =>
const ForgotPasswordOtpScreen(),
),
);
}
: null,
),
TextButton( TextButton(
onPressed: () { onPressed: () {
context.pop(); context.pop();
@@ -3,6 +3,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/forget_password_otp.dart/pages/forgot_password_otp_screen.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
@@ -53,37 +54,52 @@ class TabletForgotPasswordPage extends StatelessWidget {
), ),
), ),
SizedBox(height: 40.0.h()), SizedBox(height: 40.0.h()),
// LoginTextField(
// controller: viewModel.emailController,
// focusNode: viewModel.emailFocus,
// label: ForgotPasswordStrings.usernameHint,
// iconPath: AssetsManager.userIcon,
// isPassword: false,
// ),
// SizedBox(height: 24.0.h()),
LoginTextField( LoginTextField(
controller: viewModel.emailController, controller: viewModel.emailController,
focusNode: viewModel.emailFocus, focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.usernameHint, label: ForgotPasswordStrings.emailHint,
iconPath: AssetsManager.userIcon,
isPassword: false, isPassword: false,
), ),
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
LoginTextField( // LoginTextField(
controller: viewModel.emailController, // controller: viewModel.emailController,
focusNode: viewModel.emailFocus, // focusNode: viewModel.emailFocus,
label: ForgotPasswordStrings.emailAddressHint, // label: ForgotPasswordStrings.companyIdHint,
isPassword: false, // isPassword: false,
), // ),
SizedBox(height: 24.0.h()), // SizedBox(height: 70.0.h()),
LoginTextField( viewModel.isLoading
controller: viewModel.emailController, ? BaseButton(
focusNode: viewModel.emailFocus, isEnabled: false,
label: ForgotPasswordStrings.companyIdHint, text: ForgotPasswordStrings.resetLink,
isPassword: false, onPressed: null,
), )
SizedBox(height: 70.0.h()), : BaseButton(
BaseButton( isEnabled: viewModel.isEmailValid,
isEnabled: false, text: ForgotPasswordStrings.resetLink,
text: ForgotPasswordStrings.resetLink, onPressed:
onPressed: () async { viewModel.isEmailValid
if (viewModel.isLoginEnabled) { ? () async {
await viewModel.login(); Navigator.push(
} context,
}, MaterialPageRoute(
), builder:
(context) =>
const ForgotPasswordOtpScreen(),
),
);
}
: null,
),
SizedBox(height: 12), SizedBox(height: 12),
TextButton( TextButton(
onPressed: () { onPressed: () {
@@ -4,8 +4,8 @@ class ForgotPasswordStrings {
static const String forgotPassword = 'Forgot Password?'; static const String forgotPassword = 'Forgot Password?';
static const String forgotPasswordTitle = 'Forgot Password'; static const String forgotPasswordTitle = 'Forgot Password';
static const String forgotPasswordSubtitle = static const String forgotPasswordSubtitle =
'Enter your registered email and company ID and submit your request.'; 'Enter your registered email to submit your request';
static const String emailAddressHint = 'Email Address'; static const String emailHint = 'Email';
static const String backToSignInButton = 'Back to Sign In'; static const String backToSignInButton = 'Back to Sign In';
static const String resetLink = 'Reset Link'; static const String resetLink = 'Reset Link';
static const String usernameHint = 'Username'; static const String usernameHint = 'Username';
+14 -15
View File
@@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/routes/app_routes.dart';
import '../../../core/utils/app_toast.dart'; import '../../../core/utils/app_toast.dart';
import '../../../core/utils/size_config.dart'; import '../../../core/utils/size_config.dart';
import '../../../view_models/auth_view_model.dart'; import '../../../view_models/auth_view_model.dart';
@@ -107,20 +106,20 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
} }
}, },
), ),
SizedBox(height: 32.0), // SizedBox(height: 32.0),
TextButton( // TextButton(
onPressed: () { // onPressed: () {
ForgotPasswordRouteData().push(context); // ForgotPasswordRouteData().push(context);
}, // },
child: Text( // child: Text(
LoginStrings.forgotPassword, // LoginStrings.forgotPassword,
style: TextStyle( // style: TextStyle(
fontSize: 14.0.sp(), // fontSize: 14.0.sp(),
fontWeight: FontWeight.w500, // fontWeight: FontWeight.w500,
color: AppColors.mainBlue, // color: AppColors.mainBlue,
), // ),
), // ),
), // ),
], ],
); );
}, },
+14 -15
View File
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
@@ -103,20 +102,20 @@ class _LoginMobilePageState extends State<LoginMobilePage> {
} }
}, },
), ),
SizedBox(height: 32.0.h()), // SizedBox(height: 32.0.h()),
TextButton( // TextButton(
onPressed: () { // onPressed: () {
ForgotPasswordRouteData().push(context); // ForgotPasswordRouteData().push(context);
}, // },
child: Text( // child: Text(
LoginStrings.forgotPassword, // LoginStrings.forgotPassword,
style: TextStyle( // style: TextStyle(
fontSize: 14.0.sp(), // fontSize: 14.0.sp(),
fontWeight: FontWeight.w500, // fontWeight: FontWeight.w500,
color: AppColors.mainBlue, // color: AppColors.mainBlue,
), // ),
), // ),
), // ),
], ],
); );
}, },
+14 -15
View File
@@ -4,7 +4,6 @@ import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/routes/app_routes.dart';
import '../../../core/utils/app_toast.dart'; import '../../../core/utils/app_toast.dart';
import '../../../core/utils/size_config.dart'; import '../../../core/utils/size_config.dart';
import '../../../view_models/auth_view_model.dart'; import '../../../view_models/auth_view_model.dart';
@@ -100,20 +99,20 @@ class _LoginTabletPageState extends State<LoginTabletPage> {
} }
: null, : null,
), ),
SizedBox(height: 32.0), // SizedBox(height: 32.0),
TextButton( // TextButton(
onPressed: () { // onPressed: () {
ForgotPasswordRouteData().push(context); // ForgotPasswordRouteData().push(context);
}, // },
child: Text( // child: Text(
LoginStrings.forgotPassword, // LoginStrings.forgotPassword,
style: TextStyle( // style: TextStyle(
fontSize: 14.0.sp(), // fontSize: 14.0.sp(),
fontWeight: FontWeight.w500, // fontWeight: FontWeight.w500,
color: AppColors.mainBlue, // color: AppColors.mainBlue,
), // ),
), // ),
), // ),
], ],
); );
}, },
+24 -8
View File
@@ -37,10 +37,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.12.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -245,10 +245,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.2"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@@ -500,10 +500,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.9" version: "10.0.8"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
@@ -648,6 +648,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "6.1.0"
pinput:
dependency: "direct main"
description:
name: pinput
sha256: c41f42ee301505ae2375ec32871c985d3717bf8aee845620465b286e0140aad2
url: "https://pub.dev"
source: hosted
version: "5.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -893,6 +901,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@@ -937,10 +953,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.0.0" version: "14.3.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
+1
View File
@@ -26,6 +26,7 @@ dependencies:
country_flags: ^3.3.0 country_flags: ^3.3.0
flutter_launcher_icons: ^0.14.4 flutter_launcher_icons: ^0.14.4
toastification: ^3.0.3 toastification: ^3.0.3
pinput: ^5.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: