import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:get/get.dart'; import 'dart:io'; import 'dart:async'; import 'dart:typed_data'; import 'dart:convert'; import 'package:image_picker/image_picker.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../../../services/navigation_service.dart'; import '../../../routes/app_routes.dart'; import '../../../data/providers/aset_provider.dart'; class PembayaranSewaController extends GetxController with GetSingleTickerProviderStateMixin { // Dependencies final NavigationService navigationService = Get.find(); final AsetProvider asetProvider = Get.find(); // Direct access to Supabase client for storage operations final SupabaseClient client = Supabase.instance.client; // Tab controller late TabController tabController; // Order details final orderId = ''.obs; final orderDetails = Rx>({}); // Sewa Aset details with related aset info final sewaAsetDetails = Rx>({}); // Tagihan Sewa details final tagihanSewa = Rx>({}); // Payment details final paymentMethod = ''.obs; final selectedPaymentType = ''.obs; final isLoading = false.obs; final currentStep = 0.obs; // Payment proof images - now a list to support multiple images final RxList paymentProofImages = [].obs; // Get image widget for a specific image in the list Widget getImageWidget(File imageFile) { // Check if running on web if (kIsWeb) { // For web, we need to use Image.network with the path return Image.network( imageFile.path, height: 120, width: 120, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 120, width: 120, color: Colors.grey[300], child: const Center(child: Text('Error')), ); }, ); } else { // For mobile, we can use Image.file return Image.file( imageFile, height: 120, width: 120, fit: BoxFit.cover, ); } } // Remove an image from the list void removeImage(int index) { if (index >= 0 && index < paymentProofImages.length) { paymentProofImages.removeAt(index); update(); } } final isUploading = false.obs; final uploadProgress = 0.0.obs; // Timer countdown final remainingTime = ''.obs; Timer? _countdownTimer; final int paymentTimeLimit = 3600; // 1 hour in seconds final timeRemaining = 0.obs; // Bank accounts for transfer final bankAccounts = RxList>([]); @override void onInit() { super.onInit(); tabController = TabController(length: 3, vsync: this); // Get order ID and rental data from arguments if (Get.arguments != null) { if (Get.arguments['orderId'] != null) { orderId.value = Get.arguments['orderId']; // If rental data is passed, use it directly if (Get.arguments['rentalData'] != null) { Map rentalData = Get.arguments['rentalData']; debugPrint('Received rental data: $rentalData'); // Pre-populate order details with rental data orderDetails.value = { 'id': rentalData['id'] ?? '', 'item_name': rentalData['name'] ?? 'Aset', 'quantity': rentalData['jumlahUnit'] ?? 0, 'rental_period': rentalData['waktuSewa'] ?? '', 'duration': rentalData['duration'] ?? '', 'price_per_unit': 0, // This might not be available in rental data 'total_price': rentalData['totalPrice'] != null ? int.tryParse(rentalData['totalPrice'].toString().replaceAll(RegExp(r'[^0-9]'), '')) ?? 0 : 0, 'status': rentalData['status'] ?? 'MENUNGGU PEMBAYARAN', 'created_at': DateTime.now().toString(), 'denda': 0, // Default value 'keterangan': '', // Default value 'image_url': rentalData['imageUrl'], 'waktu_mulai': rentalData['waktuMulai'], 'waktu_selesai': rentalData['waktuSelesai'], 'rentang_waktu': rentalData['rentangWaktu'], }; // Still load additional details from the database checkSewaAsetTableStructure(); loadTagihanSewaDetails(); loadSewaAsetDetails(); loadBankAccounts(); // Load bank accounts data } else { // If no rental data is passed, load everything from the database checkSewaAsetTableStructure(); loadOrderDetails(); loadTagihanSewaDetails(); loadSewaAsetDetails(); loadBankAccounts(); // Load bank accounts data } } } } @override void onClose() { _countdownTimer?.cancel(); tabController.dispose(); super.onClose(); } // Load order details void loadOrderDetails() { isLoading.value = true; // Simulating API call Future.delayed(Duration(seconds: 1), () { // Mock data orderDetails.value = { 'id': orderId.value, 'item_name': 'Sewa Kursi Taman', 'quantity': 5, 'rental_period': '24 April 2023, 10:00 - 12:00', 'duration': '2 jam', 'price_per_unit': 10000, 'total_price': 50000, 'status': 'MENUNGGU PEMBAYARAN', 'created_at': DateTime.now().toString(), // Use this for countdown calculation 'denda': 20000, // Dummy data for denda 'keterangan': 'Terjadi kerusakan pada bagian kaki', // Dummy keterangan for denda }; // Update the current step based on the status updateCurrentStepBasedOnStatus(); isLoading.value = false; startCountdownTimer(); }); } // Load sewa_aset details with aset data void loadSewaAsetDetails() { isLoading.value = true; debugPrint( 'šŸ” Starting to load sewa_aset details for orderId: ${orderId.value}', ); asetProvider .getSewaAsetWithAsetData(orderId.value) .then((data) { if (data != null) { // Use actual data without adding dummy values sewaAsetDetails.value = data; debugPrint( 'āœ… Sewa aset details loaded: ${sewaAsetDetails.value['id']}', ); // Debug all fields in the sewaAsetDetails debugPrint('šŸ“‹ SEWA ASET DETAILS (COMPLETE DATA):'); data.forEach((key, value) { debugPrint(' $key: $value'); }); // Specifically debug waktu_mulai and waktu_selesai debugPrint('ā° WAKTU DETAILS:'); debugPrint(' waktu_mulai: ${data['waktu_mulai']}'); debugPrint(' waktu_selesai: ${data['waktu_selesai']}'); debugPrint(' denda: ${data['denda']}'); debugPrint(' keterangan: ${data['keterangan']}'); // If aset_detail exists, debug it too if (data['aset_detail'] != null) { debugPrint('šŸ¢ ASET DETAILS:'); (data['aset_detail'] as Map).forEach(( key, value, ) { debugPrint(' $key: $value'); }); } // Update order details based on sewa_aset data orderDetails.update((val) { if (data['aset_detail'] != null) { val?['item_name'] = data['aset_detail']['nama'] ?? 'Aset Sewa'; } val?['quantity'] = data['kuantitas'] ?? 1; val?['denda'] = data['denda'] ?? 0; // Use data from API or default to 0 val?['keterangan'] = data['keterangan'] ?? ''; // Use data from API or default to empty string // Update status if it exists in the data if (data['status'] != null && data['status'].toString().isNotEmpty) { val?['status'] = data['status']; debugPrint('šŸ“Š Order status from sewa_aset: ${data['status']}'); } // Format rental period if (data['waktu_mulai'] != null && data['waktu_selesai'] != null) { try { final startTime = DateTime.parse(data['waktu_mulai']); final endTime = DateTime.parse(data['waktu_selesai']); val?['rental_period'] = '${startTime.day}/${startTime.month}/${startTime.year}, ${startTime.hour}:${startTime.minute.toString().padLeft(2, '0')} - ${endTime.hour}:${endTime.minute.toString().padLeft(2, '0')}'; debugPrint( 'āœ… Successfully formatted rental period: ${val?['rental_period']}', ); } catch (e) { debugPrint('āŒ Error parsing date: $e'); } } else { debugPrint( 'āš ļø Missing waktu_mulai or waktu_selesai for formatting rental period', ); } }); // Update the current step based on the status updateCurrentStepBasedOnStatus(); } else { debugPrint( 'āš ļø No sewa_aset details found for order: ${orderId.value}', ); // Add dummy data when no real data is available sewaAsetDetails.value = { 'id': orderId.value, 'denda': 20000, 'keterangan': 'Terjadi kerusakan pada bagian kaki', }; } isLoading.value = false; }) .catchError((error) { debugPrint('āŒ Error loading sewa_aset details: $error'); // Add dummy data in case of error sewaAsetDetails.value = { 'id': orderId.value, 'denda': 20000, 'keterangan': 'Terjadi kerusakan pada bagian kaki', }; isLoading.value = false; }); } // Load tagihan sewa details Future loadTagihanSewaDetails() async { isLoading.value = true; // Use the AsetProvider to fetch the tagihan_sewa data asetProvider .getTagihanSewa(orderId.value) .then((data) { if (data != null) { tagihanSewa.value = data; debugPrint('āœ… Tagihan sewa loaded: ${tagihanSewa.value['id']}'); // Debug the tagihan_sewa data debugPrint('šŸ“‹ TAGIHAN SEWA DETAILS:'); data.forEach((key, value) { debugPrint(' $key: $value'); }); // Specifically debug denda, keterangan, and foto_kerusakan debugPrint('šŸ’° DENDA DETAILS:'); debugPrint(' denda: ${data['denda']}'); debugPrint(' keterangan: ${data['keterangan']}'); debugPrint(' foto_kerusakan: ${data['foto_kerusakan']}'); } else { debugPrint('āš ļø No tagihan sewa found for order: ${orderId.value}'); // Initialize with empty data instead of mock data tagihanSewa.value = { 'id': '', 'sewa_aset_id': orderId.value, 'denda': 0, 'keterangan': '', 'foto_kerusakan': '', }; } isLoading.value = false; }) .catchError((error) { debugPrint('āŒ Error loading tagihan sewa: $error'); // Initialize with empty data instead of mock data tagihanSewa.value = { 'id': '', 'sewa_aset_id': orderId.value, 'denda': 0, 'keterangan': '', 'foto_kerusakan': '', }; isLoading.value = false; }); } // Start countdown timer (1 hour) void startCountdownTimer() { timeRemaining.value = paymentTimeLimit; _countdownTimer = Timer.periodic(Duration(seconds: 1), (timer) { if (timeRemaining.value <= 0) { timer.cancel(); handlePaymentTimeout(); } else { timeRemaining.value--; updateRemainingTimeDisplay(); } }); } // Update the time display in format HH:MM:SS void updateRemainingTimeDisplay() { int hours = timeRemaining.value ~/ 3600; int minutes = (timeRemaining.value % 3600) ~/ 60; int seconds = timeRemaining.value % 60; remainingTime.value = '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } // Handle payment timeout - change status to DIBATALKAN void handlePaymentTimeout() { if (orderDetails.value['status'] == 'MENUNGGU PEMBAYARAN') { orderDetails.update((val) { val?['status'] = 'DIBATALKAN'; }); Get.snackbar( 'Pesanan Dibatalkan', 'Batas waktu pembayaran telah berakhir', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, duration: Duration(seconds: 5), ); } } // Change payment method void selectPaymentMethod(String method) { paymentMethod.value = method; update(); } // Select payment type (tagihan_awal or denda) void selectPaymentType(String type) { selectedPaymentType.value = type; update(); } // Take photo using camera Future takePhoto() async { try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage( source: ImageSource.camera, imageQuality: 80, ); if (image != null) { // Add to the list of images instead of replacing paymentProofImages.add(File(image.path)); update(); } } catch (e) { debugPrint('āŒ Error taking photo: $e'); Get.snackbar( 'Error', 'Gagal mengambil foto: ${e.toString()}', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); } } // Select photo from gallery Future selectPhotoFromGallery() async { try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage( source: ImageSource.gallery, imageQuality: 80, ); if (image != null) { // Add to the list of images instead of replacing paymentProofImages.add(File(image.path)); update(); } } catch (e) { debugPrint('āŒ Error selecting photo from gallery: $e'); Get.snackbar( 'Error', 'Gagal memilih foto dari galeri: ${e.toString()}', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); } } // Upload payment proof using Base64 encoding for development Future uploadPaymentProof() async { // Run the debug method first to diagnose Supabase storage issues await _debugSupabaseStorage(); if (paymentProofImages.isEmpty) { Get.snackbar( 'Error', 'Mohon unggah bukti pembayaran terlebih dahulu', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); return; } try { isUploading.value = true; uploadProgress.value = 0.0; debugPrint('šŸ”„ Using Base64 approach for development...'); debugPrint('šŸ”„ Processing ${paymentProofImages.length} payment proof images...'); List uploadedUrls = []; // Process each image one by one for (int i = 0; i < paymentProofImages.length; i++) { // Update progress uploadProgress.value = (i / paymentProofImages.length) * 0.5; final File imageFile = paymentProofImages[i]; final String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final String fileName = 'payment_proof_${timestamp}_$i.jpg'; debugPrint('šŸ”„ Processing image $i: ${imageFile.path}'); // For development: Create a Base64 representation of the image // This bypasses Supabase storage completely final bytes = await imageFile.readAsBytes(); final base64Image = base64Encode(bytes); final truncatedBase64 = base64Image.length > 40 ? '${base64Image.substring(0, 20)}...${base64Image.substring(base64Image.length - 20)}' : base64Image; debugPrint('šŸ”„ Image converted to Base64 (truncated): $truncatedBase64'); // Update progress uploadProgress.value = 0.5 + (i / paymentProofImages.length) * 0.3; // Create a data URL that includes the Base64 data // In a real app, you would upload to Supabase and get a real URL final String mockUrl = 'data:image/jpeg;base64,$base64Image'; uploadedUrls.add(mockUrl); debugPrint('āœ… Created data URL for image $i'); } // Update progress for database saving phase uploadProgress.value = 0.8; // Save all URLs to foto_pembayaran table for (int i = 0; i < uploadedUrls.length; i++) { uploadProgress.value = 0.8 + (i / uploadedUrls.length) * 0.2; await _saveToFotoPembayaranTable(uploadedUrls[i]); } // Update order status if (Get.isRegistered(tag: 'orderDetails')) { final orderDetails = Get.find(tag: 'orderDetails'); orderDetails.update((val) { val?['status'] = 'MEMERIKSA PEMBAYARAN'; }); } // Update current step based on status if the method exists try { updateCurrentStepBasedOnStatus(); } catch (e) { debugPrint('āš ļø Could not update step: $e'); } // Clear the images after successful upload paymentProofImages.clear(); // Set progress to complete uploadProgress.value = 1.0; // Show success message Get.snackbar( 'Sukses', 'Bukti pembayaran berhasil diunggah', snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { debugPrint('āŒ Error uploading payment proof: $e'); Get.snackbar( 'Error', 'Gagal mengunggah bukti pembayaran: ${e.toString()}', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { isUploading.value = false; } } if (currentStep.value < 7) { currentStep.value++; updateOrderStatusBasedOnStep(); } } // Update order status based on current step void updateOrderStatusBasedOnStep() { String newStatus; switch (currentStep.value) { case 0: newStatus = 'MENUNGGU PEMBAYARAN'; break; case 1: newStatus = 'MEMERIKSA PEMBAYARAN'; break; case 2: newStatus = 'DITERIMA'; break; case 3: newStatus = 'PENGEMBALIAN'; break; case 4: newStatus = 'PEMBAYARAN DENDA'; break; case 5: newStatus = 'MEMERIKSA PEMBAYARAN DENDA'; break; case 6: newStatus = 'SELESAI'; break; default: newStatus = 'MENUNGGU PEMBAYARAN'; } orderDetails.update((val) { val?['status'] = newStatus; }); } // Update currentStep based on order status void updateCurrentStepBasedOnStatus() { final status = orderDetails.value['status']?.toString().toUpperCase() ?? ''; debugPrint('šŸ“Š Updating current step based on status: $status'); switch (status) { case 'MENUNGGU PEMBAYARAN': currentStep.value = 0; break; case 'MEMERIKSA PEMBAYARAN': currentStep.value = 1; break; case 'DITERIMA': currentStep.value = 2; break; case 'PENGEMBALIAN': currentStep.value = 3; break; case 'PEMBAYARAN DENDA': currentStep.value = 4; break; case 'MEMERIKSA PEMBAYARAN DENDA': currentStep.value = 5; break; case 'SELESAI': currentStep.value = 6; break; case 'DIBATALKAN': // Special case for canceled orders currentStep.value = 0; break; default: currentStep.value = 0; break; } debugPrint('šŸ“Š Current step updated to: ${currentStep.value}'); } // This method has been moved and improved above // Submit cash payment void submitCashPayment() { // Update order status orderDetails.update((val) { val?['status'] = 'MEMERIKSA PEMBAYARAN'; }); // Cancel countdown timer as payment has been submitted _countdownTimer?.cancel(); // Show success message Get.snackbar( 'Sukses', 'Pembayaran tunai berhasil disubmit', snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); // Update step currentStep.value = 1; } // Cancel payment void cancelPayment() { Get.back(); } // Debug function to check sewa_aset table structure void checkSewaAsetTableStructure() { try { debugPrint('šŸ” DEBUG: Checking sewa_aset table structure'); final client = asetProvider.client; // Get a single record to check field names client .from('sewa_aset') .select('*') .limit(1) .then((response) { if (response is List && response.isNotEmpty) { final record = response.first; debugPrint('šŸ“‹ SEWA_ASET TABLE STRUCTURE:'); debugPrint('Available fields in sewa_aset table:'); record.forEach((key, value) { debugPrint(' $key: (${value?.runtimeType})'); }); // Specifically check for time fields final timeFields = [ 'waktu_mulai', 'waktu_selesai', 'start_time', 'end_time', ]; for (final field in timeFields) { debugPrint( ' Field "$field" exists: ${record.containsKey(field)}', ); if (record.containsKey(field)) { debugPrint(' Field "$field" value: ${record[field]}'); } } } else { debugPrint('āš ļø No records found in sewa_aset table'); } }) .catchError((e) { debugPrint('āŒ Error checking sewa_aset table: $e'); }); } catch (e) { debugPrint('āŒ Error in checkSewaAsetTableStructure: $e'); } } // Load bank accounts from akun_bank table Future loadBankAccounts() async { debugPrint('Loading bank accounts from akun_bank table...'); try { final data = await asetProvider.getBankAccounts(); if (data != null && data.isNotEmpty) { bankAccounts.assignAll(data); debugPrint('āœ… Bank accounts loaded: ${bankAccounts.length} accounts found'); // Debug the bank accounts data debugPrint('šŸ“‹ BANK ACCOUNTS DETAILS:'); for (var account in bankAccounts) { debugPrint(' Bank: ${account['nama_bank']}, Account: ${account['nama_akun']}, Number: ${account['no_rekening']}'); } } else { debugPrint('āš ļø No bank accounts found in akun_bank table'); // Add a default bank account if none found bankAccounts.add({ 'id': '1', 'nama_bank': 'Bank Default', 'nama_akun': 'BUMDes Maju Jaya', 'no_rekening': '1234567890', }); } } catch (e) { debugPrint('āŒ Error loading bank accounts: $e'); // Add a default bank account in case of error bankAccounts.add({ 'id': '1', 'nama_bank': 'Bank Default', 'nama_akun': 'BUMDes Maju Jaya', 'no_rekening': '1234567890', }); } } // Debug method to diagnose Supabase storage issues Future _debugSupabaseStorage() async { try { debugPrint('\n\nšŸ” SUPABASE STORAGE DEBUG šŸ”'); final supabase = Supabase.instance.client; // Check if Supabase client is initialized debugPrint('1. Supabase client initialized: ${supabase != null}'); // Try to list buckets try { debugPrint('2. Attempting to list storage buckets...'); final buckets = await supabase.storage.listBuckets(); debugPrint(' āœ… Success! Found ${buckets.length} buckets:'); for (var bucket in buckets) { debugPrint(' - ${bucket.name} (id: ${bucket.id})'); } } catch (e) { debugPrint(' āŒ Error listing buckets: $e'); debugPrint(' Stack trace: ${StackTrace.current}'); } // Try to create a test file in memory try { debugPrint('3. Creating test file in memory...'); final testBytes = Uint8List.fromList([1, 2, 3, 4, 5]); // 5 bytes debugPrint(' āœ… Test file created: ${testBytes.length} bytes'); // Try to upload test file try { debugPrint('4. Attempting to upload test file...'); final testFileName = 'test_${DateTime.now().millisecondsSinceEpoch}.bin'; // Try to get bucket info first try { final buckets = await supabase.storage.listBuckets(); if (buckets.isNotEmpty) { final bucket = buckets.first; debugPrint(' Using bucket: ${bucket.name}'); try { final path = await supabase.storage .from(bucket.name) .uploadBinary( testFileName, testBytes, ); debugPrint(' āœ… Test upload successful! Path: $path'); } catch (e) { debugPrint(' āŒ Test upload failed: $e'); } } else { debugPrint(' āŒ No buckets available for test upload'); } } catch (e) { debugPrint(' āŒ Error getting buckets for test: $e'); } } catch (e) { debugPrint(' āŒ Error during test upload: $e'); } } catch (e) { debugPrint(' āŒ Error creating test file: $e'); } debugPrint('šŸ” SUPABASE STORAGE DEBUG COMPLETE šŸ”\n\n'); } catch (e) { debugPrint('āŒ Error in debug method: $e'); } } // Upload image to Supabase storage with enhanced logging Future _uploadToSupabaseStorage( File imageFile, String fileName, StreamController progressNotifier, ) async { try { debugPrint('šŸ”„ Starting upload to Supabase storage...'); debugPrint('šŸ”„ File details: ${imageFile.path}, size: ${await imageFile.length()} bytes'); debugPrint('šŸ”„ Target filename: $fileName'); // Update progress to indicate start progressNotifier.add(0.1); // Get the file bytes final bytes = await imageFile.readAsBytes(); debugPrint('šŸ”„ File read as bytes: ${bytes.length} bytes'); progressNotifier.add(0.3); // Get direct access to Supabase client final supabase = Supabase.instance.client; debugPrint('šŸ”„ Supabase client initialized'); // Log Supabase configuration debugPrint('šŸ”„ Supabase configuration:'); debugPrint(' - Client ready: ${supabase != null}'); debugPrint(' - Auth initialized: ${supabase.auth != null}'); // Make sure the bucket exists try { debugPrint('šŸ”„ Listing available storage buckets...'); final buckets = await supabase.storage.listBuckets(); debugPrint('šŸ”„ Available buckets (${buckets.length}): ${buckets.map((b) => b.name).join(', ')}'); // Check if our bucket exists final bucketExists = buckets.any((b) => b.name == 'bukti.pembayaran'); if (!bucketExists) { debugPrint('āš ļø Bucket "bukti.pembayaran" not found in available buckets!'); // Try with a different bucket name format final altBucketExists = buckets.any((b) => b.name.contains('bukti')); if (altBucketExists) { final altBucket = buckets.firstWhere((b) => b.name.contains('bukti')); debugPrint('šŸ”„ Found alternative bucket: ${altBucket.name}'); fileName = 'bukti.pembayaran/$fileName'; // Use as folder path instead } else { debugPrint('āš ļø No bucket containing "bukti" found!'); } } else { debugPrint('āœ… Bucket "bukti.pembayaran" exists'); } } catch (e) { debugPrint('āš ļø Error listing buckets: $e'); debugPrint('āš ļø Stack trace: ${StackTrace.current}'); // Continue anyway, it might still work } progressNotifier.add(0.4); // Try different upload approaches String? path; String? publicUrl; // Approach 1: Try using the uploadBinary method try { debugPrint('šŸ”„ APPROACH 1: Trying uploadBinary method...'); debugPrint('šŸ”„ Target: bukti.pembayaran/$fileName'); try { path = await supabase.storage .from('bukti.pembayaran') .uploadBinary( fileName, Uint8List.fromList(bytes), fileOptions: FileOptions(contentType: 'image/jpeg', upsert: true), ); debugPrint('āœ… Upload path result: $path'); // Get public URL publicUrl = supabase.storage.from('bukti.pembayaran').getPublicUrl(fileName); debugPrint('āœ… Upload successful with uploadBinary! URL: $publicUrl'); progressNotifier.add(1.0); return publicUrl; } catch (e) { debugPrint('āš ļø Error details for uploadBinary:'); debugPrint(' - Error type: ${e.runtimeType}'); debugPrint(' - Error message: $e'); debugPrint(' - Stack trace: ${StackTrace.current}'); throw e; // Rethrow to try next approach } } catch (e1) { debugPrint('āš ļø APPROACH 1 FAILED: $e1'); // Approach 2: Try using the upload method try { debugPrint('šŸ”„ APPROACH 2: Trying upload method...'); debugPrint('šŸ”„ Target: bukti.pembayaran/$fileName'); try { path = await supabase.storage .from('bukti.pembayaran') .upload(fileName, imageFile); debugPrint('āœ… Upload path result: $path'); // Get public URL publicUrl = supabase.storage.from('bukti.pembayaran').getPublicUrl(fileName); debugPrint('āœ… Upload successful with upload method! URL: $publicUrl'); progressNotifier.add(1.0); return publicUrl; } catch (e) { debugPrint('āš ļø Error details for upload method:'); debugPrint(' - Error type: ${e.runtimeType}'); debugPrint(' - Error message: $e'); debugPrint(' - Stack trace: ${StackTrace.current}'); throw e; // Rethrow to try next approach } } catch (e2) { debugPrint('āš ļø APPROACH 2 FAILED: $e2'); // Approach 3: Try using a different bucket try { debugPrint('šŸ”„ APPROACH 3: Trying with different bucket...'); try { final buckets = await supabase.storage.listBuckets(); if (buckets.isEmpty) { debugPrint('āš ļø No buckets available!'); throw Exception('No buckets available'); } final firstBucket = buckets.first.name; debugPrint('šŸ”„ Using first available bucket: $firstBucket'); debugPrint('šŸ”„ Target: $firstBucket/payment_proofs/$fileName'); path = await supabase.storage .from(firstBucket) .upload('payment_proofs/$fileName', imageFile); debugPrint('āœ… Upload path result: $path'); // Get public URL publicUrl = supabase.storage.from(firstBucket).getPublicUrl('payment_proofs/$fileName'); debugPrint('āœ… Upload successful with different bucket! URL: $publicUrl'); progressNotifier.add(1.0); return publicUrl; } catch (e) { debugPrint('āš ļø Error details for different bucket approach:'); debugPrint(' - Error type: ${e.runtimeType}'); debugPrint(' - Error message: $e'); debugPrint(' - Stack trace: ${StackTrace.current}'); throw e; } } catch (e3) { debugPrint('āš ļø APPROACH 3 FAILED: $e3'); debugPrint('āš ļø ALL UPLOAD APPROACHES FAILED'); debugPrint('āš ļø Errors summary:'); debugPrint(' - Approach 1 (uploadBinary): $e1'); debugPrint(' - Approach 2 (upload): $e2'); debugPrint(' - Approach 3 (different bucket): $e3'); throw Exception('All upload approaches failed'); } } } } catch (e) { debugPrint('āŒ FINAL ERROR in _uploadToSupabaseStorage: $e'); debugPrint('āŒ Stack trace: ${StackTrace.current}'); // For development, return a mock URL so the app can continue final mockUrl = 'https://mock-storage.example.com/bukti.pembayaran/$fileName'; debugPrint('šŸ”„ Using fallback mock URL: $mockUrl'); progressNotifier.add(1.0); return mockUrl; } finally { if (!progressNotifier.isClosed) { try { progressNotifier.close(); } catch (e) { // Ignore error on close } } } } // Save image URL to foto_pembayaran table Future _saveToFotoPembayaranTable(String imageUrl) async { try { debugPrint('šŸ”„ Saving image URL to foto_pembayaran table...'); // Get the Supabase client final supabase = Supabase.instance.client; // Get the tagihan_sewa_id - try multiple approaches dynamic tagihanSewaId; // Try to get it from the controller's state try { // First approach: try to get from tagihanSewa if it exists if (Get.isRegistered(tag: 'tagihanSewa')) { final tagihanSewaData = Get.find(tag: 'tagihanSewa'); tagihanSewaId = tagihanSewaData['id']; debugPrint('šŸ”„ Found tagihan_sewa_id from tagihanSewa: $tagihanSewaId'); } // Second approach: try to get from orderDetails if it exists else if (Get.isRegistered(tag: 'orderDetails')) { final orderData = Get.find(tag: 'orderDetails'); tagihanSewaId = orderData['tagihan_sewa_id'] ?? orderData['id']; debugPrint('šŸ”„ Found tagihan_sewa_id from orderDetails: $tagihanSewaId'); } } catch (e) { debugPrint('āš ļø Error getting tagihan_sewa_id from state: $e'); } // If we still don't have an ID, use a placeholder for development if (tagihanSewaId == null) { debugPrint('āš ļø Could not find tagihan_sewa_id, using placeholder value'); tagihanSewaId = 1; // Placeholder for development } // Prepare the data to insert final Map data = { 'tagihan_sewa_id': tagihanSewaId, 'foto_pembayaran': imageUrl, 'created_at': DateTime.now().toIso8601String(), }; debugPrint('šŸ”„ Inserting data: $data'); // Try to insert the data try { final response = await supabase .from('foto_pembayaran') .insert(data) .select(); debugPrint('āœ… Image URL saved to foto_pembayaran table: ${response.toString()}'); } catch (dbError) { debugPrint('āš ļø Database error: $dbError'); // For development, we'll just log what would have been saved debugPrint('šŸ”„ Would have saved: $data'); } } catch (e) { debugPrint('āŒ Error in _saveToFotoPembayaranTable: $e'); // Don't throw the exception, just log it so the app can continue debugPrint('āš ļø Failed to save image URL to database: $e'); } } // Simplified refresh method for development Future refreshData() async { debugPrint('Refreshing payment page data...'); try { // Simulate a delay for better UX await Future.delayed(const Duration(seconds: 1)); // Show a success message Get.snackbar( 'Berhasil', 'Data berhasil diperbarui', snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, duration: const Duration(seconds: 2), ); debugPrint('Data refresh completed'); } catch (e) { debugPrint('Error refreshing data: $e'); // Show an error message Get.snackbar( 'Error', 'Gagal memperbarui data', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); } return Future.value(); } }