first commit

This commit is contained in:
Andreas Malvino
2025-06-02 22:39:03 +07:00
commit e7090af3da
245 changed files with 49210 additions and 0 deletions

View File

@ -0,0 +1,376 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/auth_controller.dart';
import '../../../theme/app_colors.dart';
class ForgotPasswordView extends GetView<AuthController> {
const ForgotPasswordView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// Background gradient
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.primarySoft, AppColors.background],
),
),
),
// Background pattern
Opacity(
opacity: 0.03,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pattern.png'),
repeat: ImageRepeat.repeat,
scale: 4.0,
),
),
),
),
// Accent circle
Positioned(
top: -100,
right: -80,
child: Container(
width: 220,
height: 220,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppColors.primary.withOpacity(0.2),
Colors.transparent,
],
),
),
),
),
// Main content
SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Back button
Padding(
padding: const EdgeInsets.all(16.0),
child: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
color: AppColors.primary,
onPressed: () => Get.back(),
),
),
// Scrollable content
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 16),
_buildHeader(),
const SizedBox(height: 40),
_buildEmailField(),
const SizedBox(height: 32),
_buildResetButton(),
const SizedBox(height: 40),
_buildImportantInfo(),
const SizedBox(height: 24),
_buildBackToLoginLink(),
const SizedBox(height: 24),
],
),
),
),
),
],
),
),
],
),
);
}
Widget _buildHeader() {
return Column(
children: [
// Floating lock icon with animation effect
Container(
width: 110,
height: 110,
decoration: BoxDecoration(
color: AppColors.surface,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColors.primary.withOpacity(0.2),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, 10),
),
],
),
child: Center(
child: Icon(
Icons.lock_open_rounded,
size: 50,
color: AppColors.primary,
),
),
),
const SizedBox(height: 32),
Text(
'Lupa Password?',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
letterSpacing: 0.5,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
'Masukkan email Anda di bawah ini dan kami akan mengirimkan link untuk reset password.',
style: TextStyle(
fontSize: 15,
color: AppColors.textSecondary,
height: 1.5,
),
textAlign: TextAlign.center,
),
],
);
}
Widget _buildEmailField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 4.0, bottom: 8.0),
child: Text(
'Email',
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
fontSize: 15,
),
),
),
Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: TextField(
controller: controller.emailController,
keyboardType: TextInputType.emailAddress,
style: TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: 'Masukkan email Anda',
hintStyle: TextStyle(color: AppColors.textLight),
prefixIcon: Icon(
Icons.email_outlined,
color: AppColors.iconGrey,
size: 22,
),
filled: true,
fillColor: AppColors.surface,
contentPadding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
),
),
),
// Error message
Obx(
() =>
controller.errorMessage.value.isNotEmpty
? Container(
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.errorLight,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
Icons.error_outline,
color: AppColors.error,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
controller.errorMessage.value,
style: TextStyle(
color: AppColors.error,
fontSize: 13,
),
),
),
],
),
)
: const SizedBox.shrink(),
),
],
);
}
Widget _buildResetButton() {
return Obx(
() => SizedBox(
height: 56,
child: ElevatedButton(
onPressed:
controller.isLoading.value ? null : controller.forgotPassword,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 2,
shadowColor: AppColors.primary.withOpacity(0.4),
disabledBackgroundColor: AppColors.primary.withOpacity(0.6),
),
child:
controller.isLoading.value
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Kirim Link Reset',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
const SizedBox(width: 8),
const Icon(Icons.send_rounded, size: 18),
],
),
),
),
);
}
Widget _buildImportantInfo() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.green.shade100),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green.shade100,
shape: BoxShape.circle,
),
child: Icon(
Icons.info_outline,
size: 20,
color: Colors.green.shade700,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Informasi Penting',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.green.shade800,
),
),
const SizedBox(height: 4),
Text(
'Petunjuk reset password akan dikirim ke email Anda. Silakan periksa kotak masuk atau folder spam setelah permintaan reset password.',
style: TextStyle(
fontSize: 13,
color: Colors.green.shade900,
height: 1.4,
),
),
],
),
),
],
),
);
}
Widget _buildBackToLoginLink() {
return Center(
child: TextButton.icon(
onPressed: () => Get.back(),
icon: Icon(
Icons.arrow_back_rounded,
size: 16,
color: AppColors.primary,
),
label: Text(
'Kembali ke Login',
style: TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
);
}
}

View File

@ -0,0 +1,368 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/auth_controller.dart';
import '../../../theme/app_colors.dart';
class LoginView extends GetView<AuthController> {
const LoginView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// Background gradient
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
AppColors.primaryLight.withOpacity(0.1),
AppColors.background,
AppColors.accentLight.withOpacity(0.1),
],
),
),
),
// Pattern overlay
Opacity(
opacity: 0.03,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pattern.png'),
repeat: ImageRepeat.repeat,
scale: 4.0,
),
),
),
),
// Accent circles
Positioned(
top: -40,
right: -20,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppColors.primary.withOpacity(0.2),
Colors.transparent,
],
),
),
),
),
Positioned(
bottom: -50,
left: -30,
child: Container(
width: 180,
height: 180,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppColors.accent.withOpacity(0.2),
Colors.transparent,
],
),
),
),
),
// Main content
SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 50),
_buildHeader(),
const SizedBox(height: 40),
_buildLoginCard(),
const SizedBox(height: 24),
_buildRegisterLink(),
const SizedBox(height: 30),
],
),
),
),
),
],
),
);
}
Widget _buildHeader() {
return Center(
child: Hero(
tag: 'logo',
child: Image.asset(
'assets/images/logo.png',
width: 220,
height: 220,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.apartment_rounded,
size: 180,
color: AppColors.primary,
);
},
),
),
);
}
Widget _buildLoginCard() {
return Card(
elevation: 4,
shadowColor: AppColors.shadow,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
child: Padding(
padding: const EdgeInsets.all(28.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Welcome text
Text(
'Selamat Datang',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
Text(
'Masuk untuk melanjutkan ke akun Anda',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 32),
// Email field
_buildInputLabel('Email'),
const SizedBox(height: 8),
_buildTextField(
controller: controller.emailController,
hintText: 'Masukkan email Anda',
prefixIcon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 24),
// Password field
_buildInputLabel('Password'),
const SizedBox(height: 8),
Obx(
() => _buildTextField(
controller: controller.passwordController,
hintText: 'Masukkan password Anda',
prefixIcon: Icons.lock_outline,
obscureText: !controller.isPasswordVisible.value,
suffixIcon: IconButton(
icon: Icon(
controller.isPasswordVisible.value
? Icons.visibility
: Icons.visibility_off,
color: AppColors.iconGrey,
),
onPressed: controller.togglePasswordVisibility,
),
),
),
// Forgot password
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => controller.goToForgotPassword(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 0,
vertical: 8,
),
),
child: Text(
'Lupa sandi?',
style: TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w500,
),
),
),
),
const SizedBox(height: 32),
// Login button
Obx(
() => SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed:
controller.isLoading.value ? null : controller.login,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.buttonText,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: controller.isLoading.value ? 0 : 2,
shadowColor: AppColors.primary.withOpacity(0.4),
),
child:
controller.isLoading.value
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Masuk',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
const SizedBox(width: 8),
const Icon(Icons.arrow_forward, size: 18),
],
),
),
),
),
// Error message
Obx(
() =>
controller.errorMessage.value.isNotEmpty
? Container(
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.errorLight,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
Icons.error_outline,
color: AppColors.error,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
controller.errorMessage.value,
style: TextStyle(
color: AppColors.error,
fontSize: 13,
),
),
),
],
),
)
: const SizedBox.shrink(),
),
],
),
),
);
}
Widget _buildInputLabel(String label) {
return Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
fontSize: 15,
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String hintText,
required IconData prefixIcon,
TextInputType keyboardType = TextInputType.text,
bool obscureText = false,
Widget? suffixIcon,
}) {
return TextField(
controller: controller,
keyboardType: keyboardType,
obscureText: obscureText,
style: TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: hintText,
hintStyle: TextStyle(color: AppColors.textLight),
prefixIcon: Icon(prefixIcon, color: AppColors.iconGrey, size: 22),
suffixIcon: suffixIcon,
filled: true,
fillColor: AppColors.inputBackground,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
),
);
}
Widget _buildRegisterLink() {
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Belum punya akun?",
style: TextStyle(color: AppColors.textSecondary),
),
TextButton(
onPressed: controller.goToSignUp,
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
child: Text(
'Daftar',
style: TextStyle(
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,266 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../theme/app_colors.dart';
class RegistrationSuccessView extends StatefulWidget {
const RegistrationSuccessView({Key? key}) : super(key: key);
@override
State<RegistrationSuccessView> createState() =>
_RegistrationSuccessViewState();
}
class _RegistrationSuccessViewState extends State<RegistrationSuccessView>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1000),
);
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.elasticOut),
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.4, 1.0, curve: Curves.easeInOut),
),
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: SafeArea(
child: Stack(
children: [
// Background elements
Positioned(
top: -120,
left: -120,
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.successLight,
),
),
),
Positioned(
right: -80,
bottom: 100,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.primaryLight.withOpacity(0.2),
),
),
),
// Confetti particles
Positioned.fill(child: _buildConfettiParticles()),
// Main content
Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildSuccessAnimation(),
const SizedBox(height: 40),
_buildSuccessMessage(),
const SizedBox(height: 40),
_buildBackToLoginButton(),
],
),
),
],
),
),
);
}
Widget _buildConfettiParticles() {
return Stack(
children: List.generate(20, (index) {
final left = (index * 20) % MediaQuery.of(context).size.width;
final top = (index * 30) % MediaQuery.of(context).size.height;
final size = 8.0 + (index % 5) * 2;
final colors = [
AppColors.success,
AppColors.primary,
AppColors.accent,
AppColors.primaryLight,
];
return Positioned(
left: left.toDouble(),
top: top.toDouble(),
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
final delay = index * 0.1;
final startTime = delay;
final endTime = startTime + 0.8;
double opacity = 0.0;
if (_animationController.value >= startTime) {
opacity =
(_animationController.value - startTime) /
(endTime - startTime);
if (opacity > 1.0) opacity = 1.0;
}
return Opacity(
opacity: opacity,
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
color: colors[index % colors.length],
shape:
index % 2 == 0 ? BoxShape.circle : BoxShape.rectangle,
borderRadius:
index % 2 == 0 ? null : BorderRadius.circular(2),
),
),
);
},
),
);
}),
);
}
Widget _buildSuccessAnimation() {
return Center(
child: Column(
children: [
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Hero(
tag: 'success',
child: Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: AppColors.success,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColors.success.withOpacity(0.3),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: const Icon(
Icons.check,
size: 70,
color: Colors.white,
),
),
),
);
},
),
],
),
);
}
Widget _buildSuccessMessage() {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Column(
children: [
Text(
'Pendaftaran Berhasil!',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Akun Anda telah berhasil terdaftar. Silakan masuk dengan email dan password yang telah Anda daftarkan.',
style: TextStyle(
fontSize: 16,
color: AppColors.textSecondary,
height: 1.5,
),
textAlign: TextAlign.center,
),
),
],
),
);
},
);
}
Widget _buildBackToLoginButton() {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: ElevatedButton(
onPressed: () {
// Navigate back to login page
Get.offAllNamed('/login');
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.buttonText,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 0,
),
child: const Text(
'Masuk Sekarang',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,549 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/auth_controller.dart';
import '../../../theme/app_colors.dart';
class RegistrationView extends GetView<AuthController> {
const RegistrationView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: SafeArea(
child: Stack(
children: [
// Background gradient
Positioned(
top: -100,
right: -100,
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppColors.primaryLight.withOpacity(0.2),
AppColors.background.withOpacity(0),
],
stops: const [0.0, 1.0],
),
),
),
),
Positioned(
bottom: -80,
left: -80,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppColors.accent.withOpacity(0.15),
AppColors.background.withOpacity(0),
],
stops: const [0.0, 1.0],
),
),
),
),
// Content
SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildBackButton(),
const SizedBox(height: 20),
_buildHeader(),
const SizedBox(height: 24),
_buildRegistrationForm(),
const SizedBox(height: 32),
_buildRegisterButton(),
const SizedBox(height: 24),
_buildImportantInfo(),
const SizedBox(height: 24),
_buildLoginLink(),
],
),
),
),
],
),
),
);
}
Widget _buildBackButton() {
return Align(
alignment: Alignment.topLeft,
child: InkWell(
onTap: () => Get.back(),
borderRadius: BorderRadius.circular(50),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColors.surface,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColors.shadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: const Icon(
Icons.arrow_back,
size: 20,
color: AppColors.primary,
),
),
),
);
}
Widget _buildHeader() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Hero(
tag: 'logo',
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: AppColors.primarySoft,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: AppColors.primary.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Icon(
Icons.apartment_rounded,
size: 40,
color: AppColors.primary,
),
),
),
const SizedBox(height: 24),
Text(
'Daftar Akun',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 10),
Text(
'Lengkapi data berikut untuk mendaftar',
style: TextStyle(fontSize: 16, color: AppColors.textSecondary),
textAlign: TextAlign.center,
),
],
);
}
Widget _buildImportantInfo() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.warningLight,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.warning.withOpacity(0.3)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.warning.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(Icons.info_outline, size: 20, color: AppColors.warning),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Informasi Penting',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.warning,
),
),
const SizedBox(height: 4),
Text(
'Pendaftaran hanya dapat dilakukan oleh warga dan mitra yang sudah terverivikasi. Silahkan hubungi petugas atau kunjungi kantor untuk informasi lebih lanjut.',
style: TextStyle(
fontSize: 13,
color: AppColors.textPrimary,
height: 1.4,
),
),
],
),
),
],
),
);
}
Widget _buildRegistrationForm() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Email Input
_buildInputLabel('Email'),
const SizedBox(height: 8),
_buildEmailField(),
const SizedBox(height: 20),
// Password Input
_buildInputLabel('Password'),
const SizedBox(height: 8),
_buildPasswordField(),
const SizedBox(height: 20),
// NIK Input
_buildInputLabel('NIK'),
const SizedBox(height: 8),
_buildNikField(),
const SizedBox(height: 20),
// Phone Number Input
_buildInputLabel('No. Hp'),
const SizedBox(height: 8),
_buildPhoneField(),
const SizedBox(height: 20),
// Role Selection Dropdown
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Daftar Sebagai',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!, width: 1),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Obx(
() => DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: controller.selectedRole.value,
hint: const Text('Pilih Peran'),
items: [
DropdownMenuItem(
value: 'WARGA',
child: const Text('Warga'),
),
DropdownMenuItem(
value: 'PETUGAS_MITRA',
child: const Text('Mitra'),
),
],
onChanged: (value) {
controller.setRole(value);
},
icon: const Icon(Icons.arrow_drop_down),
style: const TextStyle(
color: Colors.black87,
fontSize: 14,
),
),
),
),
),
),
],
),
const SizedBox(height: 20),
// Error message
Obx(
() =>
controller.errorMessage.value.isNotEmpty
? Container(
margin: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.errorLight,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
Icons.error_outline,
color: AppColors.error,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
controller.errorMessage.value,
style: TextStyle(
color: AppColors.error,
fontSize: 13,
),
),
),
],
),
)
: const SizedBox.shrink(),
),
],
);
}
Widget _buildInputLabel(String label) {
return Text(
label,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
);
}
Widget _buildEmailField() {
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: TextField(
onChanged: (value) => controller.email.value = value,
keyboardType: TextInputType.emailAddress,
style: TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: 'Masukkan email anda',
hintStyle: TextStyle(color: AppColors.textLight),
prefixIcon: Icon(Icons.email_outlined, color: AppColors.primary),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
),
),
);
}
Widget _buildPasswordField() {
return Obx(
() => Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: TextField(
onChanged: (value) => controller.password.value = value,
obscureText: !controller.isPasswordVisible.value,
style: TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: 'Masukkan password anda',
hintStyle: TextStyle(color: AppColors.textLight),
prefixIcon: Icon(Icons.lock_outlined, color: AppColors.primary),
suffixIcon: IconButton(
icon: Icon(
controller.isPasswordVisible.value
? Icons.visibility
: Icons.visibility_off,
color: AppColors.iconGrey,
),
onPressed: controller.togglePasswordVisibility,
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
),
),
),
);
}
Widget _buildNikField() {
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: TextField(
onChanged: (value) => controller.nik.value = value,
keyboardType: TextInputType.number,
style: TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: 'Masukkan NIK anda',
hintStyle: TextStyle(color: AppColors.textLight),
prefixIcon: Icon(
Icons.credit_card_outlined,
color: AppColors.primary,
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
),
),
);
}
Widget _buildPhoneField() {
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: TextField(
onChanged: (value) => controller.phoneNumber.value = value,
keyboardType: TextInputType.phone,
style: TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: 'Masukkan nomor HP anda',
hintStyle: TextStyle(color: AppColors.textLight),
prefixIcon: Icon(Icons.phone_outlined, color: AppColors.primary),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
),
),
);
}
Widget _buildRegisterButton() {
return Obx(
() => ElevatedButton(
onPressed: controller.isLoading.value ? null : controller.registerUser,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.buttonText,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 0,
disabledBackgroundColor: AppColors.primary.withOpacity(0.6),
),
child:
controller.isLoading.value
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text(
'Daftar',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
);
}
Widget _buildLoginLink() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Sudah punya akun? ',
style: TextStyle(color: AppColors.textSecondary, fontSize: 14),
),
GestureDetector(
onTap: () {
Get.back(); // Back to login page
},
child: Text(
'Masuk',
style: TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
);
}
}