modify the design of the password reset and account sign up display
This commit is contained in:
parent
e4a12c12d1
commit
d7ea2eb0ef
|
|
@ -5,29 +5,115 @@ import '../services/tts_service.dart';
|
||||||
import '../utils/colors.dart';
|
import '../utils/colors.dart';
|
||||||
import 'login_screen.dart';
|
import 'login_screen.dart';
|
||||||
|
|
||||||
class ResetPasswordScreen extends StatelessWidget {
|
class ResetPasswordScreen extends StatefulWidget {
|
||||||
|
const ResetPasswordScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ResetPasswordScreen> createState() => _ResetPasswordScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
|
||||||
final TextEditingController newPasswordController = TextEditingController();
|
final TextEditingController newPasswordController = TextEditingController();
|
||||||
final TextEditingController confirmPasswordController = TextEditingController();
|
final TextEditingController confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
final TTSService tts = TTSService();
|
final TTSService tts = TTSService();
|
||||||
|
String passwordStrength = ""; // Track password strength
|
||||||
|
|
||||||
ResetPasswordScreen({super.key});
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
tts.speak("Halaman reset password");
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ LISTENER UNTUK PASSWORD REAL-TIME ============
|
||||||
|
newPasswordController.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
passwordStrength = _getPasswordStrength(newPasswordController.text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
newPasswordController.dispose();
|
||||||
|
confirmPasswordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ FUNGSI UNTUK MENGHITUNG PASSWORD STRENGTH ============
|
||||||
|
String _getPasswordStrength(String password) {
|
||||||
|
if (password.isEmpty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Cek ada huruf besar
|
||||||
|
bool hasUpperCase = password.contains(RegExp(r'[A-Z]'));
|
||||||
|
|
||||||
|
// ✅ Cek ada angka
|
||||||
|
bool hasNumber = password.contains(RegExp(r'[0-9]'));
|
||||||
|
|
||||||
|
// ✅ Penentuan strength
|
||||||
|
if (password.length < 6) {
|
||||||
|
return "weak"; // Password lemah
|
||||||
|
} else if (hasUpperCase && hasNumber) {
|
||||||
|
return "strong"; // Password kuat
|
||||||
|
} else {
|
||||||
|
return "medium"; // Password sedang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ FUNGSI UNTUK MENDAPATKAN WARNA PASSWORD STRENGTH ============
|
||||||
|
Color _getPasswordStrengthColor() {
|
||||||
|
switch (passwordStrength) {
|
||||||
|
case "weak":
|
||||||
|
return Colors.red; // Danger
|
||||||
|
case "strong":
|
||||||
|
return Colors.green; // Success
|
||||||
|
case "medium":
|
||||||
|
return Colors.orange; // Warning
|
||||||
|
default:
|
||||||
|
return Colors.transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ FUNGSI UNTUK MENDAPATKAN TEXT PASSWORD STRENGTH ============
|
||||||
|
String _getPasswordStrengthText() {
|
||||||
|
switch (passwordStrength) {
|
||||||
|
case "weak":
|
||||||
|
return "Password lemah";
|
||||||
|
case "strong":
|
||||||
|
return "Password kuat";
|
||||||
|
case "medium":
|
||||||
|
return "Password sedang";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ HANDLE RESET PASSWORD ============
|
||||||
void resetPassword(BuildContext context) {
|
void resetPassword(BuildContext context) {
|
||||||
String newPass = newPasswordController.text;
|
String newPass = newPasswordController.text.trim();
|
||||||
String confirmPass = confirmPasswordController.text;
|
String confirmPass = confirmPasswordController.text.trim();
|
||||||
|
|
||||||
|
// ============ VALIDASI PASSWORD KOSONG ============
|
||||||
if (newPass.isEmpty || confirmPass.isEmpty) {
|
if (newPass.isEmpty || confirmPass.isEmpty) {
|
||||||
tts.speak("Semua field harus diisi");
|
tts.speak("Semua field harus diisi");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ VALIDASI PASSWORD PANJANG ============
|
||||||
|
if (newPass.length < 6) {
|
||||||
|
tts.speak("Password minimal 6 karakter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ VALIDASI PASSWORD SAMA ============
|
||||||
if (newPass != confirmPass) {
|
if (newPass != confirmPass) {
|
||||||
tts.speak("Password tidak sama");
|
tts.speak("Password tidak sama");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tts.speak("Password berhasil diubah");
|
tts.speak("Password berhasil diubah, silakan login kembali");
|
||||||
|
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
|
|
@ -38,38 +124,196 @@ class ResetPasswordScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
tts.speak("Halaman reset password");
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(title: Text("Reset Password")),
|
appBar: AppBar(
|
||||||
body: Padding(
|
title: const Text("Reset Password"),
|
||||||
padding: const EdgeInsets.all(20),
|
centerTitle: true,
|
||||||
child: Column(
|
elevation: 0,
|
||||||
children: [
|
backgroundColor: AppColors.background,
|
||||||
SizedBox(height: 20),
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// ==================== HEADER SECTION ====================
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 40),
|
||||||
|
|
||||||
CustomTextField(
|
// 🔑 ICON
|
||||||
label: "Password Baru",
|
Container(
|
||||||
controller: newPasswordController,
|
width: 70,
|
||||||
obscure: true,
|
height: 70,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primary,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.lock,
|
||||||
|
size: 36,
|
||||||
|
color: AppColors.background,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 24),
|
||||||
|
|
||||||
CustomTextField(
|
// 📝 TITLE
|
||||||
label: "Konfirmasi Password",
|
Text(
|
||||||
controller: confirmPasswordController,
|
"Buat Password Baru",
|
||||||
obscure: true,
|
style: TextStyle(
|
||||||
),
|
color: AppColors.text,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
SizedBox(height: 30),
|
SizedBox(height: 12),
|
||||||
|
|
||||||
CustomButton(
|
// 📌 SUBTITLE
|
||||||
text: "Simpan Password",
|
Text(
|
||||||
onPressed: () => resetPassword(context),
|
"Masukkan password baru untuk keamanan akun Anda",
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
],
|
style: TextStyle(
|
||||||
|
color: Colors.grey[400],
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ==================== PASSWORD SECTION ====================
|
||||||
|
Text(
|
||||||
|
"Password Baru",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 🔐 NEW PASSWORD FIELD
|
||||||
|
CustomTextField(
|
||||||
|
label: "Masukkan password baru",
|
||||||
|
controller: newPasswordController,
|
||||||
|
obscure: true,
|
||||||
|
),
|
||||||
|
|
||||||
|
// ============ PASSWORD STRENGTH INDICATOR ============
|
||||||
|
if (passwordStrength.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Text(
|
||||||
|
_getPasswordStrengthText(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: _getPasswordStrengthColor(),
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// 📌 PASSWORD REQUIREMENT INFO
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[900],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey[700]!, width: 0.5),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Requirement Password:",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
"• Minimal 6 karakter",
|
||||||
|
style: TextStyle(color: Colors.grey[400], fontSize: 12),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"• Mengandung huruf besar (A-Z)",
|
||||||
|
style: TextStyle(color: Colors.grey[400], fontSize: 12),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"• Mengandung angka (0-9)",
|
||||||
|
style: TextStyle(color: Colors.grey[400], fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 24),
|
||||||
|
|
||||||
|
// ==================== CONFIRM PASSWORD SECTION ====================
|
||||||
|
Text(
|
||||||
|
"Konfirmasi Password",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 🔐 CONFIRM PASSWORD FIELD
|
||||||
|
CustomTextField(
|
||||||
|
label: "Ulangi password baru",
|
||||||
|
controller: confirmPasswordController,
|
||||||
|
obscure: true,
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 32),
|
||||||
|
|
||||||
|
// ==================== SUBMIT BUTTON ====================
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: CustomButton(
|
||||||
|
text: "Simpan Password Baru",
|
||||||
|
onPressed: () => resetPassword(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 16),
|
||||||
|
|
||||||
|
// ==================== BACK TO LOGIN ====================
|
||||||
|
Center(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => LoginScreen()),
|
||||||
|
(route) => false,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Kembali ke Login",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
final TextEditingController phoneController = TextEditingController();
|
final TextEditingController phoneController = TextEditingController();
|
||||||
final TTSService tts = TTSService();
|
final TTSService tts = TTSService();
|
||||||
String? selectedGender;
|
String? selectedGender;
|
||||||
|
String passwordStrength = ""; // Track password strength
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -27,6 +28,13 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
tts.speak("Halaman daftar akun");
|
tts.speak("Halaman daftar akun");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============ LISTENER UNTUK PASSWORD REAL-TIME ============
|
||||||
|
passwordController.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
passwordStrength = _getPasswordStrength(passwordController.text);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -95,6 +103,56 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ FUNGSI UNTUK MENGHITUNG PASSWORD STRENGTH ============
|
||||||
|
String _getPasswordStrength(String password) {
|
||||||
|
if (password.isEmpty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Cek ada huruf besar
|
||||||
|
bool hasUpperCase = password.contains(RegExp(r'[A-Z]'));
|
||||||
|
|
||||||
|
// ✅ Cek ada angka
|
||||||
|
bool hasNumber = password.contains(RegExp(r'[0-9]'));
|
||||||
|
|
||||||
|
// ✅ Penentuan strength
|
||||||
|
if (password.length < 6) {
|
||||||
|
return "weak"; // Password lemah
|
||||||
|
} else if (hasUpperCase && hasNumber) {
|
||||||
|
return "strong"; // Password kuat
|
||||||
|
} else {
|
||||||
|
return "medium"; // Password sedang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ FUNGSI UNTUK MENDAPATKAN WARNA PASSWORD STRENGTH ============
|
||||||
|
Color _getPasswordStrengthColor() {
|
||||||
|
switch (passwordStrength) {
|
||||||
|
case "weak":
|
||||||
|
return Colors.red; // Danger
|
||||||
|
case "strong":
|
||||||
|
return Colors.green; // Success
|
||||||
|
case "medium":
|
||||||
|
return Colors.orange; // Warning
|
||||||
|
default:
|
||||||
|
return Colors.transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ FUNGSI UNTUK MENDAPATKAN TEXT PASSWORD STRENGTH ============
|
||||||
|
String _getPasswordStrengthText() {
|
||||||
|
switch (passwordStrength) {
|
||||||
|
case "weak":
|
||||||
|
return "Password lemah";
|
||||||
|
case "strong":
|
||||||
|
return "Password kuat";
|
||||||
|
case "medium":
|
||||||
|
return "Password sedang";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildGenderOption(String gender) {
|
Widget buildGenderOption(String gender) {
|
||||||
return RadioListTile<String>(
|
return RadioListTile<String>(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
|
@ -271,6 +329,20 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
obscure: true,
|
obscure: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ============ PASSWORD STRENGTH INDICATOR ============
|
||||||
|
if (passwordStrength.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Text(
|
||||||
|
_getPasswordStrengthText(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: _getPasswordStrengthColor(),
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
|
|
||||||
CustomTextField(
|
CustomTextField(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue