modify design forgot password, register account, otp code
This commit is contained in:
parent
7f3b71a40b
commit
e4a12c12d1
|
|
@ -11,19 +11,28 @@ class ForgotPasswordScreen extends StatelessWidget {
|
|||
|
||||
ForgotPasswordScreen({super.key});
|
||||
|
||||
void handleReset(BuildContext context) {
|
||||
String email = emailController.text;
|
||||
// ============ HANDLE RESET PASSWORD ============
|
||||
void handleReset(BuildContext context) {
|
||||
String email = emailController.text.trim();
|
||||
|
||||
if (email.isEmpty) {
|
||||
tts.speak("Email harus diisi");
|
||||
return;
|
||||
// ============ VALIDASI EMAIL KOSONG ============
|
||||
if (email.isEmpty) {
|
||||
tts.speak("Email harus diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
// ============ VALIDASI EMAIL WAJIB "@" ============
|
||||
if (!email.contains("@")) {
|
||||
tts.speak("Email harus mengandung simbol @");
|
||||
return;
|
||||
}
|
||||
|
||||
tts.speak("OTP telah dikirim ke email Anda");
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => OTPScreen()),
|
||||
);
|
||||
}
|
||||
tts.speak("OTP telah dikirim ke email ");
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => OTPScreen()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -32,33 +41,128 @@ class ForgotPasswordScreen extends StatelessWidget {
|
|||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: AppBar(
|
||||
title: Text("Forgot Password"),
|
||||
title: const Text("Lupa Password"),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColors.background,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
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),
|
||||
|
||||
Text(
|
||||
"Masukkan email untuk reset password",
|
||||
style: TextStyle(color: AppColors.text),
|
||||
),
|
||||
// 🔐 ICON
|
||||
Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.lock_reset,
|
||||
size: 36,
|
||||
color: AppColors.background,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
SizedBox(height: 24),
|
||||
|
||||
CustomTextField(
|
||||
label: "Email",
|
||||
controller: emailController,
|
||||
),
|
||||
// 📝 TITLE
|
||||
Text(
|
||||
"Reset Password",
|
||||
style: TextStyle(
|
||||
color: AppColors.text,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 30),
|
||||
SizedBox(height: 12),
|
||||
|
||||
CustomButton(
|
||||
text: "Kirim OTP",
|
||||
onPressed: () => handleReset(context),
|
||||
),
|
||||
],
|
||||
// 📌 SUBTITLE
|
||||
Text(
|
||||
"Masukkan email Anda untuk menerima kode OTP",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[400],
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ==================== FORM SECTION ====================
|
||||
// 📧 EMAIL LABEL
|
||||
Text(
|
||||
"Email Address",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 8),
|
||||
|
||||
// 📧 EMAIL FIELD
|
||||
CustomTextField(
|
||||
label: "Masukkan email Anda",
|
||||
controller: emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
|
||||
SizedBox(height: 12),
|
||||
|
||||
// 📌 INFO TEXT
|
||||
Text(
|
||||
"Kami akan mengirimkan kode OTP ke email ini untuk verifikasi",
|
||||
style: TextStyle(
|
||||
color: Colors.grey[500],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 40),
|
||||
|
||||
// ==================== SUBMIT BUTTON ====================
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: CustomButton(
|
||||
text: "Kirim Kode OTP",
|
||||
onPressed: () => handleReset(context),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
|
||||
// ==================== BACK BUTTON ====================
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
"Kembali ke Login",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../widgets/custom_textfield.dart';
|
||||
import '../widgets/custom_button.dart';
|
||||
import '../services/tts_service.dart';
|
||||
import '../utils/colors.dart';
|
||||
|
|
@ -10,64 +12,200 @@ class OTPScreen extends StatelessWidget {
|
|||
|
||||
OTPScreen({super.key});
|
||||
|
||||
// ============ VERIFY OTP CODE ============
|
||||
void verifyOTP(BuildContext context) {
|
||||
String otp = otpController.text;
|
||||
String otp = otpController.text.trim();
|
||||
|
||||
// ============ VALIDASI OTP KOSONG ============
|
||||
if (otp.isEmpty) {
|
||||
tts.speak("Kode OTP harus diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔥 simulasi OTP benar
|
||||
if (otp == "1234") {
|
||||
tts.speak("OTP benar");
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => ResetPasswordScreen()),
|
||||
);
|
||||
} else {
|
||||
tts.speak("OTP salah");
|
||||
// ============ VALIDASI OTP HARUS 6 DIGIT ============
|
||||
if (otp.length < 6) {
|
||||
tts.speak("Kode OTP harus 6 digit");
|
||||
return;
|
||||
}
|
||||
|
||||
// ============ LANGSUNG KE RESET PASSWORD (TANPA DATABASE) ============
|
||||
// NOTE: Fitur ini bypass validasi karena belum ada database
|
||||
tts.speak("Kode OTP valid, lanjut ke reset password");
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => ResetPasswordScreen()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
tts.speak("Masukkan kode OTP");
|
||||
tts.speak("Halaman verifikasi kode OTP");
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: AppBar(title: Text("Verifikasi OTP")),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 30),
|
||||
appBar: AppBar(
|
||||
title: const Text("Verifikasi OTP"),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColors.background,
|
||||
),
|
||||
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),
|
||||
|
||||
Text(
|
||||
"Masukkan kode OTP",
|
||||
style: TextStyle(color: AppColors.text),
|
||||
),
|
||||
// 📬 ICON
|
||||
Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.mail_outline,
|
||||
size: 36,
|
||||
color: AppColors.background,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
SizedBox(height: 24),
|
||||
|
||||
TextField(
|
||||
controller: otpController,
|
||||
keyboardType: TextInputType.number,
|
||||
style: TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: "OTP",
|
||||
labelStyle: TextStyle(color: Colors.white),
|
||||
// 📝 TITLE
|
||||
Text(
|
||||
"Verifikasi Kode OTP",
|
||||
style: TextStyle(
|
||||
color: AppColors.text,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 12),
|
||||
|
||||
// 📌 SUBTITLE
|
||||
Text(
|
||||
"Masukkan kode 6 digit yang telah dikirim ke email Anda",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[400],
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 30),
|
||||
// ==================== FORM SECTION ====================
|
||||
// 🔐 OTP LABEL
|
||||
Text(
|
||||
"Kode OTP",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
CustomButton(
|
||||
text: "Verifikasi",
|
||||
onPressed: () => verifyOTP(context),
|
||||
),
|
||||
],
|
||||
SizedBox(height: 8),
|
||||
|
||||
// 🔐 OTP FIELD - MAKSIMAL 6 DIGIT
|
||||
CustomTextField(
|
||||
label: "Masukkan 6 digit kode",
|
||||
controller: otpController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(6),
|
||||
],
|
||||
maxLength: 6,
|
||||
),
|
||||
|
||||
SizedBox(height: 12),
|
||||
|
||||
// 📌 INFO TEXT
|
||||
Text(
|
||||
"Kode ini hanya berlaku selama 1 menit setelah dikirim",
|
||||
style: TextStyle(
|
||||
color: Colors.grey[500],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 40),
|
||||
|
||||
// ==================== VERIFY BUTTON ====================
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: CustomButton(
|
||||
text: "Verifikasi Kode",
|
||||
onPressed: () => verifyOTP(context),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
|
||||
// ==================== RESEND CODE SECTION ====================
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Tidak menerima kode?",
|
||||
style: TextStyle(color: Colors.grey[400], fontSize: 13),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
tts.speak("Kode OTP telah dikirim ulang");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text("Kode OTP telah dikirim ulang ke email Anda"),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
"Kirim Ulang Kode",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
|
||||
// ==================== BACK BUTTON ====================
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
"Kembali",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../widgets/custom_textfield.dart';
|
||||
import '../widgets/custom_button.dart';
|
||||
import '../services/tts_service.dart';
|
||||
|
|
@ -15,6 +16,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
|||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final TextEditingController confirmPasswordController = TextEditingController();
|
||||
final TextEditingController nameController = TextEditingController();
|
||||
final TextEditingController phoneController = TextEditingController();
|
||||
final TTSService tts = TTSService();
|
||||
String? selectedGender;
|
||||
|
||||
|
|
@ -28,6 +31,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
phoneController.dispose();
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
confirmPasswordController.dispose();
|
||||
|
|
@ -35,12 +40,43 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
|||
}
|
||||
|
||||
void handleSignUp(BuildContext context) {
|
||||
String name = nameController.text;
|
||||
String phone = phoneController.text;
|
||||
String email = emailController.text;
|
||||
String password = passwordController.text;
|
||||
String confirm = confirmPasswordController.text;
|
||||
|
||||
if (email.isEmpty || password.isEmpty) {
|
||||
tts.speak("Email dan password wajib diisi");
|
||||
// ============ VALIDASI NAMA ============
|
||||
if (name.isEmpty) {
|
||||
tts.speak("Nama lengkap wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
// ============ VALIDASI TELEPON ============
|
||||
if (phone.isEmpty) {
|
||||
tts.speak("Nomer telepon wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
if (phone.length < 10) {
|
||||
tts.speak("Nomer telepon minimal 10 angka");
|
||||
return;
|
||||
}
|
||||
|
||||
// ============ VALIDASI EMAIL ============
|
||||
if (email.isEmpty) {
|
||||
tts.speak("Email wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!email.contains("@")) {
|
||||
tts.speak("Email harus mengandung simbol @");
|
||||
return;
|
||||
}
|
||||
|
||||
// ============ VALIDASI PASSWORD ============
|
||||
if (password.isEmpty) {
|
||||
tts.speak("Password wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +85,7 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
|||
return;
|
||||
}
|
||||
|
||||
// ============ VALIDASI JENIS KELAMIN ============
|
||||
if (selectedGender == null) {
|
||||
tts.speak("Jenis kelamin wajib dipilih");
|
||||
return;
|
||||
|
|
@ -91,72 +128,187 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
|||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: AppBar(
|
||||
title: const Text("Sign Up"),
|
||||
title: const Text("Buat Akun Baru"),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColors.background,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
|
||||
CustomTextField(
|
||||
label: "Email",
|
||||
controller: emailController,
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
CustomTextField(
|
||||
label: "Password",
|
||||
controller: passwordController,
|
||||
obscure: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
CustomTextField(
|
||||
label: "Konfirmasi Password",
|
||||
controller: confirmPasswordController,
|
||||
obscure: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Semantics(
|
||||
label: "Pilih jenis kelamin",
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.text),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ==================== HEADER SECTION ====================
|
||||
Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Jenis Kelamin",
|
||||
style: TextStyle(
|
||||
color: AppColors.text,
|
||||
fontSize: 16,
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.person_add,
|
||||
size: 32,
|
||||
color: AppColors.background,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
"Daftar Akun",
|
||||
style: TextStyle(
|
||||
color: AppColors.text,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
"Isi data diri Anda untuk membuat akun",
|
||||
style: TextStyle(
|
||||
color: Colors.grey[400],
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
buildGenderOption("Laki-laki"),
|
||||
const Divider(color: Colors.white24, height: 1),
|
||||
buildGenderOption("Perempuan"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
SizedBox(height: 32),
|
||||
|
||||
CustomButton(
|
||||
text: "Daftar",
|
||||
onPressed: () => handleSignUp(context),
|
||||
),
|
||||
],
|
||||
// ==================== PERSONAL INFO SECTION ====================
|
||||
Text(
|
||||
"Data Pribadi",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
CustomTextField(
|
||||
label: "Nama Lengkap",
|
||||
controller: nameController,
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
CustomTextField(
|
||||
label: "Nomer Telepon",
|
||||
controller: phoneController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(13),
|
||||
],
|
||||
maxLength: 13,
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
|
||||
// ==================== GENDER SELECTION ====================
|
||||
Text(
|
||||
"Jenis Kelamin",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 12),
|
||||
|
||||
Semantics(
|
||||
label: "Pilih jenis kelamin",
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.text, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildGenderOption("Laki-laki"),
|
||||
const Divider(color: Colors.white24, height: 8),
|
||||
buildGenderOption("Perempuan"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 28),
|
||||
|
||||
// ==================== CREDENTIALS SECTION ====================
|
||||
Text(
|
||||
"Keamanan Akun",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
CustomTextField(
|
||||
label: "Email",
|
||||
controller: emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
CustomTextField(
|
||||
label: "Password",
|
||||
controller: passwordController,
|
||||
obscure: true,
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
CustomTextField(
|
||||
label: "Konfirmasi Password",
|
||||
controller: confirmPasswordController,
|
||||
obscure: true,
|
||||
),
|
||||
|
||||
SizedBox(height: 32),
|
||||
|
||||
// ==================== SUBMIT BUTTON ====================
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: CustomButton(
|
||||
text: "Daftar Sekarang",
|
||||
onPressed: () => handleSignUp(context),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// ==================== BACK TO LOGIN ====================
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
"Kembali ke Login",
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,28 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../utils/colors.dart';
|
||||
|
||||
class CustomTextField extends StatelessWidget {
|
||||
class CustomTextField extends StatefulWidget {
|
||||
final String label;
|
||||
final TextEditingController controller;
|
||||
final bool obscure;
|
||||
final TextInputType? keyboardType;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final int? maxLength;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
const CustomTextField({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.controller,
|
||||
this.obscure = false,
|
||||
this.keyboardType,
|
||||
this.inputFormatters,
|
||||
this.maxLength,
|
||||
this.validator,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomTextField> createState() => _CustomTextFieldState();
|
||||
}
|
||||
|
||||
class _CustomTextFieldState extends State<CustomTextField> {
|
||||
late bool _obscureText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_obscureText = widget.obscure;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
label: "Input $label",
|
||||
label: "Input ${widget.label}",
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
obscureText: obscure,
|
||||
controller: widget.controller,
|
||||
obscureText: _obscureText,
|
||||
keyboardType: widget.keyboardType,
|
||||
inputFormatters: widget.inputFormatters,
|
||||
maxLength: widget.maxLength,
|
||||
style: TextStyle(color: AppColors.text),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelText: widget.label,
|
||||
labelStyle: TextStyle(color: AppColors.text),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: AppColors.text),
|
||||
|
|
@ -30,6 +55,20 @@ class CustomTextField extends StatelessWidget {
|
|||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: AppColors.primary),
|
||||
),
|
||||
// ============ TOGGLE PASSWORD VISIBILITY ============
|
||||
suffixIcon: widget.obscure
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
_obscureText ? Icons.visibility_off : Icons.visibility,
|
||||
color: AppColors.text,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue