823 lines
26 KiB
Dart
823 lines
26 KiB
Dart
import 'package:get/get.dart';
|
|
import 'package:flutter/material.dart';
|
|
import '../../../data/providers/auth_provider.dart';
|
|
import '../../../routes/app_routes.dart';
|
|
import '../../../services/navigation_service.dart';
|
|
import '../../../data/providers/aset_provider.dart';
|
|
import '../../../theme/app_colors.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
|
|
class WargaDashboardController extends GetxController {
|
|
// Dependency injection
|
|
final AuthProvider _authProvider = Get.find<AuthProvider>();
|
|
final NavigationService navigationService = Get.find<NavigationService>();
|
|
final AsetProvider _asetProvider = Get.find<AsetProvider>();
|
|
|
|
// User data
|
|
final userName = 'Pengguna Warga'.obs;
|
|
final userRole = 'Warga'.obs;
|
|
final userAvatar = Rx<String?>(null);
|
|
final userEmail = ''.obs;
|
|
final userNik = ''.obs;
|
|
final userPhone = ''.obs;
|
|
final userAddress = ''.obs;
|
|
final userTanggalLahir = ''.obs;
|
|
final userRtRw = ''.obs;
|
|
final userKelurahanDesa = ''.obs;
|
|
final userKecamatan = ''.obs;
|
|
|
|
// Navigation state is now managed by NavigationService
|
|
|
|
// Sample data (would be loaded from API)
|
|
final activeRentals = <Map<String, dynamic>>[].obs;
|
|
|
|
// Active bills
|
|
final activeBills = <Map<String, dynamic>>[].obs;
|
|
|
|
// Active penalties
|
|
final activePenalties = <Map<String, dynamic>>[].obs;
|
|
|
|
// Summary counts
|
|
final diterimaCount = 0.obs;
|
|
final tagihanAktifCount = 0.obs;
|
|
final dendaAktifCount = 0.obs;
|
|
|
|
@override
|
|
void onInit() async {
|
|
super.onInit();
|
|
|
|
// Set navigation index to Home (0)
|
|
navigationService.setNavIndex(0);
|
|
|
|
// Check if navigation is coming from login
|
|
final args = Get.arguments;
|
|
final bool isFromLogin = args != null && args['from_login'] == true;
|
|
|
|
if (isFromLogin) {
|
|
print('onInit: Navigation from login detected, prioritizing data fetch');
|
|
}
|
|
|
|
// Verifikasi bahwa pengguna sudah login sebelum melakukan fetch data
|
|
if (_authProvider.currentUser != null) {
|
|
// Prioritize loading user profile data first
|
|
await fetchProfileFromWargaDesa();
|
|
|
|
// If the profile data was not loaded successfully, try again after a short delay
|
|
if (userName.value == 'Pengguna Warga' || userNik.value.isEmpty) {
|
|
print('onInit: Profile data not loaded, retrying after delay');
|
|
await Future.delayed(const Duration(milliseconds: 800));
|
|
await fetchProfileFromWargaDesa();
|
|
}
|
|
|
|
// Load other user data
|
|
await _loadUserData();
|
|
|
|
// Load other data in parallel to speed up the dashboard initialization
|
|
Future.wait([
|
|
_loadSampleData(),
|
|
loadDummyData(),
|
|
loadUnpaidRentals(),
|
|
_debugCountSewaAset(),
|
|
loadActiveRentals(),
|
|
]).then((_) => print('onInit: All data loaded successfully'));
|
|
|
|
// If coming from login, make sure UI is updated
|
|
if (isFromLogin) {
|
|
update();
|
|
}
|
|
} else {
|
|
print('onInit: User not logged in, skipping data fetch');
|
|
}
|
|
}
|
|
|
|
Future<void> _loadUserData() async {
|
|
try {
|
|
// Get the full name from warga_desa table
|
|
final fullName = await _authProvider.getUserFullName();
|
|
if (fullName != null && fullName.isNotEmpty) {
|
|
userName.value = fullName;
|
|
}
|
|
|
|
// Get the avatar URL
|
|
final avatar = await _authProvider.getUserAvatar();
|
|
userAvatar.value = avatar;
|
|
|
|
// Get the role name
|
|
final roleId = await _authProvider.getUserRoleId();
|
|
if (roleId != null) {
|
|
final roleName = await _authProvider.getRoleName(roleId);
|
|
if (roleName != null) {
|
|
userRole.value = roleName;
|
|
}
|
|
}
|
|
|
|
// Load additional user data
|
|
// In a real app, these would come from the API/database
|
|
userEmail.value = await _authProvider.getUserEmail() ?? '';
|
|
userNik.value = await _authProvider.getUserNIK() ?? '';
|
|
userPhone.value = await _authProvider.getUserPhone() ?? '';
|
|
userAddress.value = await _authProvider.getUserAddress() ?? '';
|
|
|
|
// Load additional profile data
|
|
final tanggalLahir = await _authProvider.getUserTanggalLahir();
|
|
final rtRw = await _authProvider.getUserRtRw();
|
|
final kelurahanDesa = await _authProvider.getUserKelurahanDesa();
|
|
final kecamatan = await _authProvider.getUserKecamatan();
|
|
|
|
// Set values for additional profile data
|
|
userTanggalLahir.value = tanggalLahir ?? 'Tidak tersedia';
|
|
userRtRw.value = rtRw ?? 'Tidak tersedia';
|
|
userKelurahanDesa.value = kelurahanDesa ?? 'Tidak tersedia';
|
|
userKecamatan.value = kecamatan ?? 'Tidak tersedia';
|
|
} catch (e) {
|
|
print('Error loading user data: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _loadSampleData() async {
|
|
// Clear any existing data
|
|
activeRentals.clear();
|
|
|
|
// Load active rentals from API
|
|
// For now, using sample data
|
|
activeRentals.add({
|
|
'id': '1',
|
|
'name': 'Kursi',
|
|
'time': '24 April 2023, 10:00 - 12:00',
|
|
'duration': '2 jam',
|
|
'price': 'Rp50.000',
|
|
'can_extend': true,
|
|
});
|
|
}
|
|
|
|
void extendRental(String rentalId) {
|
|
// Implementasi untuk memperpanjang sewa
|
|
// Seharusnya melakukan API call ke backend
|
|
}
|
|
|
|
void endRental(String rentalId) {
|
|
// Implementasi untuk mengakhiri sewa
|
|
// Seharusnya melakukan API call ke backend
|
|
}
|
|
|
|
void navigateToRentals() {
|
|
// Navigate to SewaAset using the navigation service
|
|
navigationService.toSewaAset();
|
|
}
|
|
|
|
Future<void> refreshData() async {
|
|
print('refreshData: Refreshing dashboard data');
|
|
try {
|
|
// First fetch profile data
|
|
await fetchProfileFromWargaDesa();
|
|
await _loadUserData();
|
|
|
|
// Then load all other data in parallel
|
|
await Future.wait([
|
|
_loadSampleData(),
|
|
loadDummyData(),
|
|
loadUnpaidRentals(),
|
|
loadActiveRentals(),
|
|
_debugCountSewaAset(),
|
|
]);
|
|
|
|
// Update UI
|
|
update();
|
|
print('refreshData: Dashboard data refreshed successfully');
|
|
} catch (e) {
|
|
print('refreshData: Error refreshing data: $e');
|
|
// Show error message to user
|
|
Get.snackbar(
|
|
'Perhatian',
|
|
'Terjadi kesalahan saat memuat data',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade900,
|
|
duration: const Duration(seconds: 3),
|
|
);
|
|
}
|
|
}
|
|
|
|
void onNavItemTapped(int index) {
|
|
if (navigationService.currentNavIndex.value == index) {
|
|
return; // Don't do anything if same tab
|
|
}
|
|
|
|
navigationService.setNavIndex(index);
|
|
|
|
switch (index) {
|
|
case 0:
|
|
// Already on Home tab
|
|
break;
|
|
case 1:
|
|
// Navigate to Sewa page, tab Aktif
|
|
toWargaSewaTabAktif();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void toWargaSewaTabAktif() {
|
|
// Navigasi ke halaman warga sewa dan tab Aktif (index 3)
|
|
Get.toNamed(Routes.WARGA_SEWA, arguments: {'tab': 3});
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
print('logout: Logging out user');
|
|
await _authProvider.signOut();
|
|
navigationService.toLogin();
|
|
print('logout: User logged out and redirected to login screen');
|
|
}
|
|
|
|
Future<void> loadDummyData() async {
|
|
// Dummy active bills
|
|
activeBills.clear();
|
|
activeBills.add({
|
|
'id': '1',
|
|
'title': 'Tagihan Air',
|
|
'due_date': '30 Apr 2023',
|
|
'amount': 'Rp 125.000',
|
|
});
|
|
activeBills.add({
|
|
'id': '2',
|
|
'title': 'Sewa Aula Desa',
|
|
'due_date': '15 Apr 2023',
|
|
'amount': 'Rp 350.000',
|
|
});
|
|
|
|
// Dummy active penalties
|
|
activePenalties.clear();
|
|
activePenalties.add({
|
|
'id': '1',
|
|
'title': 'Keterlambatan Sewa Traktor',
|
|
'days_late': '7',
|
|
'amount': 'Rp 75.000',
|
|
});
|
|
}
|
|
|
|
Future<void> loadUnpaidRentals() async {
|
|
try {
|
|
final results = await _authProvider.getSewaAsetByStatus([
|
|
'MENUNGGU PEMBAYARAN',
|
|
'PEMBAYARANAN DENDA',
|
|
]);
|
|
activeBills.value = results;
|
|
} catch (e) {
|
|
print('Error loading unpaid rentals: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _debugCountSewaAset() async {
|
|
diterimaCount.value = await _asetProvider.countSewaAsetByStatus([
|
|
'DITERIMA',
|
|
]);
|
|
tagihanAktifCount.value = await _asetProvider.countSewaAsetByStatus([
|
|
'MENUNGGU PEMBAYARAN',
|
|
'PERIKSA PEMBAYARAN',
|
|
]);
|
|
dendaAktifCount.value = await _asetProvider.countSewaAsetByStatus([
|
|
'PEMBAYARAN DENDA',
|
|
'PERIKSA PEMBAYARAN DENDA',
|
|
]);
|
|
print('[DEBUG] Jumlah sewa diterima: ${diterimaCount.value}');
|
|
print('[DEBUG] Jumlah tagihan aktif: ${tagihanAktifCount.value}');
|
|
print('[DEBUG] Jumlah denda aktif: ${dendaAktifCount.value}');
|
|
}
|
|
|
|
Future<void> loadActiveRentals() async {
|
|
try {
|
|
activeRentals.clear();
|
|
final sewaAsetList = await _authProvider.getSewaAsetByStatus(['AKTIF']);
|
|
for (var sewaAset in sewaAsetList) {
|
|
String assetName = 'Aset';
|
|
String? imageUrl;
|
|
String namaSatuanWaktu = sewaAset['nama_satuan_waktu'] ?? 'jam';
|
|
if (sewaAset['aset_id'] != null) {
|
|
final asetData = await _asetProvider.getAsetById(sewaAset['aset_id']);
|
|
if (asetData != null) {
|
|
assetName = asetData.nama;
|
|
imageUrl = asetData.imageUrl;
|
|
}
|
|
}
|
|
DateTime? waktuMulai;
|
|
DateTime? waktuSelesai;
|
|
String waktuSewa = '';
|
|
String tanggalSewa = '';
|
|
String jamMulai = '';
|
|
String jamSelesai = '';
|
|
String rentangWaktu = '';
|
|
if (sewaAset['waktu_mulai'] != null &&
|
|
sewaAset['waktu_selesai'] != null) {
|
|
waktuMulai = DateTime.parse(sewaAset['waktu_mulai']);
|
|
waktuSelesai = DateTime.parse(sewaAset['waktu_selesai']);
|
|
final formatTanggal = DateFormat('dd-MM-yyyy');
|
|
final formatWaktu = DateFormat('HH:mm');
|
|
final formatTanggalLengkap = DateFormat('dd MMMM yyyy', 'id_ID');
|
|
tanggalSewa = formatTanggalLengkap.format(waktuMulai);
|
|
jamMulai = formatWaktu.format(waktuMulai);
|
|
jamSelesai = formatWaktu.format(waktuSelesai);
|
|
if (namaSatuanWaktu.toLowerCase() == 'jam') {
|
|
rentangWaktu = '$jamMulai - $jamSelesai';
|
|
} else if (namaSatuanWaktu.toLowerCase() == 'hari') {
|
|
final tanggalMulai = formatTanggalLengkap.format(waktuMulai);
|
|
final tanggalSelesai = formatTanggalLengkap.format(waktuSelesai);
|
|
rentangWaktu = '$tanggalMulai - $tanggalSelesai';
|
|
} else {
|
|
rentangWaktu = '$jamMulai - $jamSelesai';
|
|
}
|
|
waktuSewa =
|
|
'${formatTanggal.format(waktuMulai)} | ${formatWaktu.format(waktuMulai)} - '
|
|
'${formatTanggal.format(waktuSelesai)} | ${formatWaktu.format(waktuSelesai)}';
|
|
}
|
|
String totalPrice = 'Rp 0';
|
|
if (sewaAset['total'] != null) {
|
|
final formatter = NumberFormat.currency(
|
|
locale: 'id',
|
|
symbol: 'Rp ',
|
|
decimalDigits: 0,
|
|
);
|
|
totalPrice = formatter.format(sewaAset['total']);
|
|
}
|
|
String duration = '-';
|
|
final tagihan = await _asetProvider.getTagihanSewa(sewaAset['id']);
|
|
if (tagihan != null) {
|
|
final durasiTagihan = tagihan['durasi'] ?? sewaAset['durasi'];
|
|
final satuanTagihan = tagihan['nama_satuan_waktu'] ?? namaSatuanWaktu;
|
|
duration = '${durasiTagihan ?? '-'} ${satuanTagihan ?? ''}';
|
|
} else {
|
|
duration = '${sewaAset['durasi'] ?? '-'} ${namaSatuanWaktu ?? ''}';
|
|
}
|
|
activeRentals.add({
|
|
'id': sewaAset['id'] ?? '',
|
|
'name': assetName,
|
|
'imageUrl': imageUrl ?? 'assets/images/gambar_pendukung.jpg',
|
|
'jumlahUnit': sewaAset['kuantitas'] ?? 0,
|
|
'waktuSewa': waktuSewa,
|
|
'duration': duration,
|
|
'status': sewaAset['status'] ?? 'AKTIF',
|
|
'totalPrice': totalPrice,
|
|
'tanggalSewa': tanggalSewa,
|
|
'jamMulai': jamMulai,
|
|
'jamSelesai': jamSelesai,
|
|
'rentangWaktu': rentangWaktu,
|
|
'namaSatuanWaktu': namaSatuanWaktu,
|
|
'waktuMulai': sewaAset['waktu_mulai'],
|
|
'waktuSelesai': sewaAset['waktu_selesai'],
|
|
'can_extend': sewaAset['can_extend'] == true,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
print('Error loading active rentals: $e');
|
|
}
|
|
}
|
|
|
|
void toSewaAsetTabPaket() {
|
|
// Navigasi ke halaman sewa_aset tab Paket (index 1)
|
|
Get.toNamed(Routes.SEWA_ASET, arguments: {'tab': 1});
|
|
}
|
|
|
|
Future<void> fetchProfileFromWargaDesa() async {
|
|
try {
|
|
final user = _authProvider.currentUser;
|
|
if (user == null) {
|
|
print(
|
|
'fetchProfileFromWargaDesa: No current user found, skipping fetch',
|
|
);
|
|
return; // Exit early if no user is logged in
|
|
}
|
|
|
|
final userId = user.id;
|
|
print('fetchProfileFromWargaDesa: Fetching data for user: $userId');
|
|
|
|
final data =
|
|
await _authProvider.client
|
|
.from('warga_desa')
|
|
.select('nik, alamat, email, nama_lengkap, no_hp, avatar')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (data != null) {
|
|
print('fetchProfileFromWargaDesa: Data retrieved successfully');
|
|
userNik.value = data['nik']?.toString() ?? '';
|
|
userAddress.value = data['alamat']?.toString() ?? '';
|
|
userEmail.value = data['email']?.toString() ?? '';
|
|
userName.value = data['nama_lengkap']?.toString() ?? '';
|
|
userPhone.value = data['no_hp']?.toString() ?? '';
|
|
userAvatar.value = data['avatar']?.toString() ?? '';
|
|
|
|
// Trigger UI refresh
|
|
update();
|
|
print('fetchProfileFromWargaDesa: Profile data updated');
|
|
} else {
|
|
print('fetchProfileFromWargaDesa: No data found for user: $userId');
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching profile from warga_desa: $e');
|
|
// If it fails, try again after a delay
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
try {
|
|
await _retryFetchProfile();
|
|
} catch (retryError) {
|
|
print('Retry error fetching profile: $retryError');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper method to retry fetching profile
|
|
Future<void> _retryFetchProfile() async {
|
|
final user = _authProvider.currentUser;
|
|
if (user == null) {
|
|
print('_retryFetchProfile: No current user found, skipping retry');
|
|
return; // Exit early if no user is logged in
|
|
}
|
|
|
|
print('_retryFetchProfile: Retrying fetch for user: ${user.id}');
|
|
|
|
final data =
|
|
await _authProvider.client
|
|
.from('warga_desa')
|
|
.select('nik, alamat, email, nama_lengkap, no_hp, avatar')
|
|
.eq('user_id', user.id)
|
|
.maybeSingle();
|
|
|
|
if (data != null) {
|
|
print('_retryFetchProfile: Data retrieved successfully on retry');
|
|
userNik.value = data['nik']?.toString() ?? '';
|
|
userAddress.value = data['alamat']?.toString() ?? '';
|
|
userEmail.value = data['email']?.toString() ?? '';
|
|
userName.value = data['nama_lengkap']?.toString() ?? '';
|
|
userPhone.value = data['no_hp']?.toString() ?? '';
|
|
userAvatar.value = data['avatar']?.toString() ?? '';
|
|
update();
|
|
print('_retryFetchProfile: Profile data updated');
|
|
}
|
|
}
|
|
|
|
// Method to update user profile data in warga_desa table
|
|
Future<bool> updateUserProfile({
|
|
required String namaLengkap,
|
|
required String noHp,
|
|
}) async {
|
|
try {
|
|
final user = _authProvider.currentUser;
|
|
if (user == null) {
|
|
print('Cannot update profile: No current user');
|
|
return false;
|
|
}
|
|
|
|
final userId = user.id;
|
|
|
|
// Update data in warga_desa table
|
|
await _authProvider.client
|
|
.from('warga_desa')
|
|
.update({'nama_lengkap': namaLengkap, 'no_hp': noHp})
|
|
.eq('user_id', userId);
|
|
|
|
// Update local values
|
|
userName.value = namaLengkap;
|
|
userPhone.value = noHp;
|
|
|
|
print('Profile updated successfully for user: $userId');
|
|
return true;
|
|
} catch (e) {
|
|
print('Error updating user profile: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Method to delete user avatar
|
|
Future<bool> deleteUserAvatar() async {
|
|
try {
|
|
final user = _authProvider.currentUser;
|
|
if (user == null) {
|
|
print('Cannot delete avatar: No current user');
|
|
return false;
|
|
}
|
|
|
|
final userId = user.id;
|
|
final currentAvatarUrl = userAvatar.value;
|
|
|
|
// If there's an avatar URL, delete it from storage
|
|
if (currentAvatarUrl != null && currentAvatarUrl.isNotEmpty) {
|
|
try {
|
|
print('Attempting to delete avatar from URL: $currentAvatarUrl');
|
|
|
|
// Extract filename from URL
|
|
// The URL format is typically:
|
|
// https://[project-ref].supabase.co/storage/v1/object/public/warga/[filename]
|
|
|
|
final uri = Uri.parse(currentAvatarUrl);
|
|
final path = uri.path;
|
|
|
|
// Find the filename after the last slash
|
|
final filename = path.substring(path.lastIndexOf('/') + 1);
|
|
|
|
if (filename.isNotEmpty) {
|
|
print('Extracted filename: $filename');
|
|
|
|
// Delete from storage bucket 'warga'
|
|
final response = await _authProvider.client.storage
|
|
.from('warga')
|
|
.remove([filename]);
|
|
|
|
print('Storage deletion response: $response');
|
|
} else {
|
|
print('Failed to extract filename from avatar URL');
|
|
}
|
|
} catch (e) {
|
|
print('Error deleting avatar from storage: $e');
|
|
// Continue with database update even if storage delete fails
|
|
}
|
|
}
|
|
|
|
// Update warga_desa table to set avatar to null
|
|
await _authProvider.client
|
|
.from('warga_desa')
|
|
.update({'avatar': null})
|
|
.eq('user_id', userId);
|
|
|
|
// Update local value
|
|
userAvatar.value = '';
|
|
|
|
print('Avatar deleted successfully for user: $userId');
|
|
return true;
|
|
} catch (e) {
|
|
print('Error deleting user avatar: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Method to update user avatar URL
|
|
Future<bool> updateUserAvatar(String avatarUrl) async {
|
|
try {
|
|
final user = _authProvider.currentUser;
|
|
if (user == null) {
|
|
print('Cannot update avatar: No current user');
|
|
return false;
|
|
}
|
|
|
|
final userId = user.id;
|
|
|
|
// Update data in warga_desa table
|
|
await _authProvider.client
|
|
.from('warga_desa')
|
|
.update({'avatar': avatarUrl})
|
|
.eq('user_id', userId);
|
|
|
|
// Update local value
|
|
userAvatar.value = avatarUrl;
|
|
|
|
print('Avatar updated successfully for user: $userId');
|
|
return true;
|
|
} catch (e) {
|
|
print('Error updating user avatar: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Method to upload avatar image to Supabase storage
|
|
Future<String?> uploadAvatar(Uint8List fileBytes, String fileName) async {
|
|
try {
|
|
final user = _authProvider.currentUser;
|
|
if (user == null) {
|
|
print('Cannot upload avatar: No current user');
|
|
return null;
|
|
}
|
|
|
|
// Generate a unique filename using timestamp and user ID
|
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
|
final extension = fileName.split('.').last;
|
|
final uniqueFileName = 'avatar_${user.id}_$timestamp.$extension';
|
|
|
|
// Upload to 'warga' bucket
|
|
final response = await _authProvider.client.storage
|
|
.from('warga')
|
|
.uploadBinary(
|
|
uniqueFileName,
|
|
fileBytes,
|
|
fileOptions: const FileOptions(cacheControl: '3600', upsert: true),
|
|
);
|
|
|
|
// Get the public URL
|
|
final publicUrl = _authProvider.client.storage
|
|
.from('warga')
|
|
.getPublicUrl(uniqueFileName);
|
|
|
|
print('Avatar uploaded successfully: $publicUrl');
|
|
return publicUrl;
|
|
} catch (e) {
|
|
print('Error uploading avatar: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Method to handle image picking from camera or gallery
|
|
Future<XFile?> pickImage(ImageSource source) async {
|
|
try {
|
|
// Pick image directly without permission checks
|
|
final ImagePicker picker = ImagePicker();
|
|
final XFile? pickedFile = await picker.pickImage(
|
|
source: source,
|
|
maxWidth: 800,
|
|
maxHeight: 800,
|
|
imageQuality: 85,
|
|
);
|
|
|
|
if (pickedFile != null) {
|
|
print('Image picked: ${pickedFile.path}');
|
|
}
|
|
return pickedFile;
|
|
} catch (e) {
|
|
print('Error picking image: $e');
|
|
|
|
// Show error message if there's an issue
|
|
Get.snackbar(
|
|
'Gagal',
|
|
'Tidak dapat mengakses ${source == ImageSource.camera ? 'kamera' : 'galeri'}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red.shade700,
|
|
colorText: Colors.white,
|
|
duration: const Duration(seconds: 3),
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Method to show image source selection dialog
|
|
Future<void> showImageSourceDialog() async {
|
|
await Get.bottomSheet(
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(20),
|
|
topRight: Radius.circular(20),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 4,
|
|
margin: const EdgeInsets.only(bottom: 20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade300,
|
|
borderRadius: BorderRadius.circular(2),
|
|
),
|
|
),
|
|
const Text(
|
|
'Pilih Sumber Gambar',
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
_buildImageSourceOption(
|
|
icon: Icons.camera_alt_rounded,
|
|
label: 'Kamera',
|
|
onTap: () async {
|
|
Get.back();
|
|
final pickedFile = await pickImage(ImageSource.camera);
|
|
if (pickedFile != null) {
|
|
await processPickedImage(pickedFile);
|
|
}
|
|
},
|
|
),
|
|
_buildImageSourceOption(
|
|
icon: Icons.photo_library_rounded,
|
|
label: 'Galeri',
|
|
onTap: () async {
|
|
Get.back();
|
|
final pickedFile = await pickImage(ImageSource.gallery);
|
|
if (pickedFile != null) {
|
|
await processPickedImage(pickedFile);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
isDismissible: true,
|
|
enableDrag: true,
|
|
);
|
|
}
|
|
|
|
// Helper method to build image source option
|
|
Widget _buildImageSourceOption({
|
|
required IconData icon,
|
|
required String label,
|
|
required VoidCallback onTap,
|
|
}) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(icon, color: AppColors.primary, size: 32),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Method to process picked image (temporary preview before saving)
|
|
Future<void> processPickedImage(XFile pickedFile) async {
|
|
try {
|
|
// Read file as bytes
|
|
final bytes = await pickedFile.readAsBytes();
|
|
|
|
// Store the picked file temporarily for later use when saving
|
|
tempPickedFile.value = pickedFile;
|
|
|
|
// Update UI with temporary avatar preview
|
|
tempAvatarBytes.value = bytes;
|
|
|
|
print('Image processed for preview');
|
|
} catch (e) {
|
|
print('Error processing picked image: $e');
|
|
}
|
|
}
|
|
|
|
// Method to save the picked image to Supabase and update profile
|
|
Future<bool> saveNewAvatar() async {
|
|
try {
|
|
if (tempPickedFile.value == null || tempAvatarBytes.value == null) {
|
|
print('No temporary image to save');
|
|
return false;
|
|
}
|
|
|
|
final pickedFile = tempPickedFile.value!;
|
|
final bytes = tempAvatarBytes.value!;
|
|
|
|
// First delete the old avatar if exists
|
|
final currentAvatarUrl = userAvatar.value;
|
|
if (currentAvatarUrl != null && currentAvatarUrl.isNotEmpty) {
|
|
try {
|
|
await deleteUserAvatar();
|
|
} catch (e) {
|
|
print('Error deleting old avatar: $e');
|
|
// Continue with upload even if delete fails
|
|
}
|
|
}
|
|
|
|
// Upload new avatar
|
|
final newAvatarUrl = await uploadAvatar(bytes, pickedFile.name);
|
|
if (newAvatarUrl == null) {
|
|
print('Failed to upload new avatar');
|
|
return false;
|
|
}
|
|
|
|
// Update avatar URL in database
|
|
final success = await updateUserAvatar(newAvatarUrl);
|
|
|
|
if (success) {
|
|
// Clear temporary data
|
|
tempPickedFile.value = null;
|
|
tempAvatarBytes.value = null;
|
|
|
|
print('Avatar updated successfully');
|
|
}
|
|
|
|
return success;
|
|
} catch (e) {
|
|
print('Error saving new avatar: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Method to cancel avatar change
|
|
void cancelAvatarChange() {
|
|
tempPickedFile.value = null;
|
|
tempAvatarBytes.value = null;
|
|
print('Avatar change canceled');
|
|
}
|
|
|
|
// Temporary storage for picked image
|
|
final Rx<XFile?> tempPickedFile = Rx<XFile?>(null);
|
|
final Rx<Uint8List?> tempAvatarBytes = Rx<Uint8List?>(null);
|
|
}
|