Files
penyaluran_app/lib/app/modules/auth/controllers/auth_controller.dart

672 lines
21 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
import 'package:penyaluran_app/app/data/models/petugas_desa_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/warga_model.dart';
import 'package:penyaluran_app/app/data/providers/auth_provider.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
class AuthController extends GetxController {
static AuthController get to => Get.find();
final AuthProvider _authProvider = AuthProvider();
final Rx<UserData?> _userData = Rx<UserData?>(null);
UserData? get userData => _userData.value;
// Getter untuk BaseUserModel
BaseUserModel? get baseUser => _userData.value?.baseUser;
// Getter untuk role
String get role => _userData.value?.baseUser.roleName.toLowerCase() ?? '';
// Getter dinamis untuk data role-specific
dynamic get roleData => _userData.value?.roleData;
// Getter untuk memeriksa tipe roleData
bool get isWarga =>
_userData.value?.roleData is WargaModel && role == 'warga';
bool get isDonatur =>
_userData.value?.roleData is DonaturModel && role == 'donatur';
bool get isPetugasDesa =>
_userData.value?.roleData is PetugasDesaModel && role == 'petugas_desa';
// Helper method untuk mendapatkan nama display
String get displayName {
if (roleData == null) return baseUser?.email ?? 'Pengguna';
if (isWarga) {
return (roleData as WargaModel).displayName;
} else if (isDonatur) {
return (roleData as DonaturModel).displayName;
} else if (isPetugasDesa) {
return (roleData as PetugasDesaModel).displayName;
}
return baseUser?.email ?? 'Pengguna';
}
final RxBool isLoading = false.obs;
final RxBool isWargaProfileComplete = false.obs;
// Variable untuk mengontrol visibility password
final RxBool isPasswordHidden = true.obs;
final RxBool isConfirmPasswordHidden = true.obs;
// Flag untuk menandai apakah sudah melakukan pengambilan data profil
final RxBool _hasLoadedProfile = false.obs;
// Form controllers
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final TextEditingController confirmPasswordController =
TextEditingController();
// Form keys
final GlobalKey<FormState> loginFormKey = GlobalKey<FormState>();
final GlobalKey<FormState> wargaProfileFormKey = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
// Tambahkan penundaan kecil untuk menghindari loop navigasi
Future.delayed(const Duration(milliseconds: 100), () {
checkAuthStatus();
});
}
@override
void onClose() {
// Pastikan semua controller dibersihkan sebelum dilepaskan
clearAndDisposeControllers();
// Dispose controller registrasi donatur
confirmPasswordController.dispose();
namaController.dispose();
alamatController.dispose();
noHpController.dispose();
jenisController.dispose();
super.onClose();
}
// Metode untuk membersihkan dan melepaskan controller
void clearAndDisposeControllers() {
try {
if (emailController.text.isNotEmpty) emailController.clear();
if (passwordController.text.isNotEmpty) passwordController.clear();
if (confirmPasswordController.text.isNotEmpty) {
confirmPasswordController.clear();
}
emailController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
} catch (e) {
print('Error disposing controllers: $e');
}
}
// Memeriksa status autentikasi
Future<void> checkAuthStatus() async {
if (isLoading.value) {
return; // Hindari pemanggilan berulang jika sedang loading
}
isLoading.value = true;
try {
print('Memeriksa status autentikasi...');
// Jika user sudah ada di memori dan profil sudah diambil, gunakan data yang ada
if (_userData.value != null && _hasLoadedProfile.value) {
print('Menggunakan data user yang sudah ada di memori');
_handleAuthenticatedUser(_userData.value!);
return;
}
// Jika belum ada data user, ambil dari provider
final currentUserData = await _authProvider.getCurrentUser();
if (currentUserData != null) {
print(
'User terautentikasi: ${currentUserData.baseUser.email}, role: ${currentUserData.baseUser.roleName}');
_userData.value = currentUserData;
_hasLoadedProfile.value = true;
_handleAuthenticatedUser(currentUserData);
} else {
print('Tidak ada user yang terautentikasi');
_handleUnauthenticatedUser();
}
} catch (e) {
print('Error checking auth status: $e');
print('Stack trace: ${StackTrace.current}');
_handleUnauthenticatedUser();
} finally {
isLoading.value = false;
print('Pemeriksaan status autentikasi selesai');
}
}
// Metode untuk menangani user yang terautentikasi
void _handleAuthenticatedUser(UserData userData) {
// Hindari navigasi jika sudah berada di halaman yang sesuai
final currentRoute = Get.currentRoute;
print('Rute saat ini: $currentRoute');
// Dapatkan role dari BaseUserModel
final role = userData.baseUser.roleName.toLowerCase();
print('Role yang digunakan: $role');
// Untuk semua role, arahkan ke dashboard masing-masing
final targetRoute = _getTargetRouteForRole(role);
print('Target rute: $targetRoute');
// Jika berada di splash atau login, navigasi ke dashboard
if (currentRoute == Routes.splash || currentRoute == Routes.login) {
print('Navigasi ke rute target berdasarkan role');
navigateBasedOnRole(role);
} else if (currentRoute != targetRoute) {
// Jika berada di rute lain yang tidak sesuai dengan role, navigasi ke dashboard
print('Berada di rute yang tidak sesuai, navigasi ke rute target');
navigateBasedOnRole(role);
} else {
print('Sudah berada di rute yang sesuai, tidak perlu navigasi');
}
}
// Metode untuk menangani user yang tidak terautentikasi
void _handleUnauthenticatedUser() {
// Jika tidak ada user yang login, arahkan ke halaman login
if (Get.currentRoute != Routes.login) {
print('Navigasi ke halaman login');
// Bersihkan dependensi form sebelum navigasi
clearFormDependencies();
Get.offAllNamed(Routes.login);
} else {
print('Sudah berada di halaman login');
}
}
// Memeriksa status profil warga
Future<void> checkWargaProfileStatus() async {
try {
if (role == 'warga') {
isWargaProfileComplete.value = roleData != null;
} else {
isWargaProfileComplete.value = true;
}
} catch (e) {
print('Error checking warga profile: $e');
isWargaProfileComplete.value = false;
}
}
// Metode untuk membersihkan dependensi form
void clearFormDependencies() {
try {
// Hapus fokus dari semua field
FocusManager.instance.primaryFocus?.unfocus();
// Reset form state jika ada
loginFormKey.currentState?.reset();
wargaProfileFormKey.currentState?.reset();
} catch (e) {
print('Error clearing form dependencies: $e');
}
}
// Metode untuk navigasi berdasarkan peran
void navigateBasedOnRole(String role) {
// Bersihkan dependensi form sebelum navigasi
clearFormDependencies();
switch (role.toLowerCase()) {
case 'warga':
Get.offAllNamed(Routes.wargaDashboard);
break;
case 'petugas_desa':
Get.offAllNamed(Routes.petugasDesaDashboard);
break;
case 'donatur':
Get.offAllNamed(Routes.donaturDashboard);
break;
default:
Get.offAllNamed(Routes.home);
break;
}
}
// Metode untuk login
Future<void> login() async {
print('DEBUG: Memulai proses login');
if (loginFormKey.currentState == null) {
print('Error: loginFormKey.currentState adalah null');
print('DEBUG: Form key: $loginFormKey');
Get.snackbar(
'Error',
'Terjadi kesalahan pada form login. Silakan coba lagi.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
print('DEBUG: Form state ditemukan, melakukan validasi');
if (!loginFormKey.currentState!.validate()) {
print('DEBUG: Validasi form gagal');
return;
}
// Simpan nilai dari controller sebelum melakukan operasi asinkron
final email = emailController.text.trim();
final password = passwordController.text;
print('DEBUG: Email: $email, Password length: ${password.length}');
try {
print('DEBUG: Mengatur isLoading ke true');
isLoading.value = true;
print('DEBUG: Memanggil _authProvider.signIn');
final userData = await _authProvider.signIn(
email,
password,
);
print('DEBUG: Hasil signIn: ${userData != null ? 'Berhasil' : 'Gagal'}');
if (userData != null) {
print('DEBUG: User ditemukan, role: ${userData.baseUser.roleName}');
_userData.value = userData;
_hasLoadedProfile.value = true; // Tandai bahwa profil sudah diambil
clearControllers();
// Arahkan ke dashboard sesuai peran
print(
'DEBUG: Navigasi berdasarkan peran: ${userData.baseUser.roleName}');
navigateBasedOnRole(userData.baseUser.roleName.toLowerCase());
} else {
print('DEBUG: User null setelah login berhasil');
handleLoginError(Exception('Data pengguna tidak ditemukan'));
}
} catch (e) {
handleLoginError(e);
} finally {
print('DEBUG: Mengatur isLoading ke false');
isLoading.value = false;
}
}
// Menangani error saat login
void handleLoginError(dynamic error) {
print('DEBUG: Error login: $error');
String errorMessage = 'Terjadi kesalahan saat login. Silakan coba lagi.';
if (error.toString().contains('Invalid login credentials')) {
errorMessage = 'Email atau password salah. Silakan coba lagi.';
} else if (error.toString().contains('Too many requests')) {
errorMessage = 'Terlalu banyak percobaan login. Silakan coba lagi nanti.';
}
Get.snackbar(
'Error',
errorMessage,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
// Metode untuk membersihkan controller
void clearControllers() {
emailController.clear();
passwordController.clear();
confirmPasswordController.clear();
}
// Metode untuk logout
Future<void> logout() async {
try {
isLoading.value = true;
await _authProvider.signOut();
_userData.value = null;
_hasLoadedProfile.value = false;
Get.offAllNamed(Routes.login);
} catch (e) {
print('Error saat logout: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat logout. Silakan coba lagi.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
// Validasi email
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email tidak boleh kosong';
}
if (!GetUtils.isEmail(value)) {
return 'Email tidak valid';
}
return null;
}
// Validasi password
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password tidak boleh kosong';
}
if (value.length < 6) {
return 'Password minimal 6 karakter';
}
return null;
}
// Validasi konfirmasi password
String? validateConfirmPassword(String? value) {
if (value == null || value.isEmpty) {
return 'Konfirmasi password tidak boleh kosong';
}
if (value != passwordController.text) {
return 'Password dan konfirmasi password tidak sama';
}
return null;
}
// Metode untuk reset password
Future<void> resetPassword(String email) async {
if (email.isEmpty) {
Get.snackbar(
'Error',
'Email tidak boleh kosong',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
if (!GetUtils.isEmail(email)) {
Get.snackbar(
'Error',
'Email tidak valid',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
try {
isLoading.value = true;
// Menggunakan Supabase untuk reset password
await _authProvider.resetPasswordForEmail(
email,
redirectTo: 'penyaluranapp://reset-password',
);
Get.snackbar(
'Sukses',
'Instruksi reset password telah dikirim ke email Anda',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
// Kembali ke halaman login
Future.delayed(const Duration(seconds: 3), () {
Get.offNamed(Routes.login);
});
} catch (e) {
print('Error saat reset password: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat mengirim reset password. Silakan coba lagi nanti.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
// Metode untuk refresh data user setelah update profil
Future<void> refreshUserData() async {
try {
print('Memperbarui data pengguna...');
isLoading.value = true;
// Hapus cache profil yang sudah tidak valid
_hasLoadedProfile.value = false;
// Ambil data user terbaru dari provider dengan menskip cache
final currentUserData =
await _authProvider.getCurrentUser(skipCache: true);
if (currentUserData != null) {
print(
'Data pengguna berhasil diperbarui: ${currentUserData.baseUser.name}');
_userData.value = currentUserData;
_hasLoadedProfile.value = true;
} else {
print('Gagal memperbarui data pengguna');
}
} catch (e) {
print('Error refreshing user data: $e');
} finally {
isLoading.value = false;
}
}
// Mendapatkan rute target berdasarkan role
String _getTargetRouteForRole(String role) {
switch (role.toLowerCase()) {
case 'warga':
return Routes.wargaDashboard;
case 'petugas_desa':
return Routes.petugasDesaDashboard;
case 'donatur':
return Routes.donaturDashboard;
default:
return Routes.home;
}
}
// Metode untuk validasi form registrasi donatur
String? validateDonaturNama(String? value) {
if (value == null || value.isEmpty) {
return 'Nama lengkap tidak boleh kosong';
}
return null;
}
String? validateDonaturNoHp(String? value) {
if (value == null || value.isEmpty) {
return 'Nomor HP tidak boleh kosong';
}
if (!RegExp(r'^[0-9]+$').hasMatch(value)) {
return 'Nomor HP hanya boleh berisi angka';
}
return null;
}
String? validateDonaturAlamat(String? value) {
if (value == null || value.isEmpty) {
return 'Alamat tidak boleh kosong';
}
return null;
}
// Form controller untuk registrasi donatur
final TextEditingController namaController = TextEditingController();
final TextEditingController alamatController = TextEditingController();
final TextEditingController noHpController = TextEditingController();
final TextEditingController jenisController = TextEditingController();
// Form key untuk registrasi donatur
final GlobalKey<FormState> registerDonaturFormKey = GlobalKey<FormState>();
// Metode untuk registrasi donatur
Future<void> registerDonatur() async {
print('DEBUG: Memulai proses registrasi donatur');
if (registerDonaturFormKey.currentState == null) {
print('Error: registerDonaturFormKey.currentState adalah null');
Get.snackbar(
'Error',
'Terjadi kesalahan pada form registrasi. Silakan coba lagi.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
if (!registerDonaturFormKey.currentState!.validate()) {
print('DEBUG: Validasi form gagal');
return;
}
isLoading.value = true;
// Kita akan mencoba maksimal 2 kali jika terjadi error
int maxAttempts = 2;
int attempt = 0;
bool success = false;
Exception? lastError;
while (attempt < maxAttempts && !success) {
attempt++;
print(
'DEBUG: Mencoba pendaftaran donatur (percobaan ke-$attempt dari $maxAttempts)');
try {
print('DEBUG: Validasi form berhasil, melanjutkan proses registrasi');
print('DEBUG: Email: ${emailController.text}');
print('DEBUG: Nama: ${namaController.text}');
print('DEBUG: Alamat: ${alamatController.text}');
print('DEBUG: No HP: ${noHpController.text}');
print(
'DEBUG: Jenis: ${jenisController.text.isEmpty ? 'Individu' : jenisController.text}');
// Proses registrasi donatur
await _authProvider.signUpDonatur(
email: emailController.text,
password: passwordController.text,
namaLengkap: namaController.text,
alamat: alamatController.text,
noHp: noHpController.text,
jenis:
jenisController.text.isEmpty ? 'Individu' : jenisController.text,
);
print('DEBUG: Registrasi donatur berhasil');
success = true;
Get.snackbar(
'Sukses',
'Registrasi donatur berhasil! Silakan login dengan akun Anda.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 5),
);
// Bersihkan form
clearDonaturRegistrationForm();
// Arahkan ke halaman login
Get.offAllNamed(Routes.login);
} catch (e) {
lastError = e is Exception ? e : Exception(e.toString());
print('ERROR: Error registrasi donatur (percobaan ke-$attempt): $e');
print('ERROR Detail: ${e.runtimeType} - ${e.toString()}');
// Cek apakah bisa dicoba lagi atau tidak
bool canRetry = false;
if (e.toString().contains('Database error saving new user')) {
canRetry = true; // Error database bisa dicoba lagi
print('DEBUG: Error database, akan mencoba lagi');
// Delay sedikit sebelum mencoba lagi
await Future.delayed(const Duration(seconds: 1));
} else if (e.toString().contains('User already registered')) {
// Jangan coba lagi jika email sudah terdaftar
canRetry = false;
}
if (!canRetry || attempt >= maxAttempts) {
// Jika tidak bisa retry atau sudah maksimal, tampilkan error
String errorMessage = 'Gagal melakukan registrasi';
// Tangani error sesuai jenisnya
if (e.toString().contains('email konfirmasi')) {
errorMessage =
'Gagal mengirim email konfirmasi. Mohon periksa alamat email Anda dan coba lagi nanti.';
} else if (e.toString().contains('Email sudah terdaftar')) {
errorMessage =
'Email sudah terdaftar. Silakan gunakan email lain atau login dengan email tersebut.';
} else if (e.toString().contains('weak-password')) {
errorMessage =
'Password terlalu lemah. Gunakan kombinasi huruf, angka, dan simbol.';
} else if (e.toString().contains('invalid-email')) {
errorMessage = 'Format email tidak valid.';
} else if (e.toString().contains('Database error')) {
errorMessage =
'Terjadi kesalahan pada database. Silakan coba lagi nanti atau hubungi administrator.';
} else {
errorMessage = 'Gagal melakukan registrasi: ${e.toString()}';
}
Get.snackbar(
'Error',
errorMessage,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 7),
);
break; // Keluar dari loop
}
}
}
isLoading.value = false;
// Jika gagal setelah mencoba beberapa kali
if (!success && lastError != null) {
print('ERROR: Pendaftaran gagal setelah $attempt percobaan');
// Error sudah ditampilkan di dalam loop
}
}
// Metode untuk membersihkan form registrasi donatur
void clearDonaturRegistrationForm() {
emailController.clear();
passwordController.clear();
confirmPasswordController.clear();
namaController.clear();
alamatController.clear();
noHpController.clear();
jenisController.clear();
}
// Metode untuk toggle visibility password
void togglePasswordVisibility() {
isPasswordHidden.value = !isPasswordHidden.value;
}
// Metode untuk toggle visibility konfirmasi password
void toggleConfirmPasswordVisibility() {
isConfirmPasswordHidden.value = !isConfirmPasswordHidden.value;
}
}