From 3051732b1eb86c69c96011974a1f3e6548315c3d Mon Sep 17 00:00:00 2001 From: shaulascr Date: Sun, 13 Apr 2025 19:17:43 +0700 Subject: [PATCH] fix bgt order (-paymentId) --- .../data/api/dto/CourierCostRequest.kt | 2 +- .../api/response/order/OrderDetailResponse.kt | 215 +++++---- .../product/DetailStoreProductResponse.kt | 2 + .../data/repository/OrderRepository.kt | 41 +- .../ui/order/CheckoutActivity.kt | 16 +- .../ui/order/CheckoutViewModel.kt | 80 ++-- .../ui/order/PaymentMethodAdapter.kt | 45 +- .../ui/order/ShippingActivity.kt | 55 ++- .../ui/order/ShippingViewModel.kt | 10 +- .../ui/order/address/AddAddressActivity.kt | 408 ++++++++++++++---- .../ui/order/address/AddAddressViewModel.kt | 108 +++-- .../ui/order/address/ProvinceAdapter.kt | 3 + .../main/res/layout/activity_add_address.xml | 79 +++- app/src/main/res/layout/activity_checkout.xml | 3 +- app/src/main/res/layout/activity_shipping.xml | 10 + 15 files changed, 777 insertions(+), 300 deletions(-) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt index 0387371..712bb93 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt @@ -7,7 +7,7 @@ data class CourierCostRequest( val addressId: Int, @SerializedName("items") - val itemCost: CostProduct + val itemCost: List ) data class CostProduct ( diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt index fd145f6..20b6432 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt @@ -4,17 +4,131 @@ import com.google.gson.annotations.SerializedName data class OrderDetailResponse( - @field:SerializedName("orders") + @field:SerializedName("orders") val orders: Orders, - @field:SerializedName("message") + @field:SerializedName("message") val message: String ) +data class Orders( + + @field:SerializedName("receipt_num") + val receiptNum: String, + + @field:SerializedName("payment_upload_at") + val paymentUploadAt: String, + + @field:SerializedName("latitude") + val latitude: String, + + @field:SerializedName("pay_info_name") + val payInfoName: String, + + @field:SerializedName("created_at") + val createdAt: String, + + @field:SerializedName("voucher_code") + val voucherCode: Any, + + @field:SerializedName("updated_at") + val updatedAt: String, + + @field:SerializedName("etd") + val etd: String, + + @field:SerializedName("street") + val street: String, + + @field:SerializedName("cancel_date") + val cancelDate: String, + + @field:SerializedName("payment_evidence") + val paymentEvidence: String, + + @field:SerializedName("longitude") + val longitude: String, + + @field:SerializedName("shipment_status") + val shipmentStatus: String, + + @field:SerializedName("order_items") + val orderItems: List, + + @field:SerializedName("auto_completed_at") + val autoCompletedAt: String, + + @field:SerializedName("is_store_location") + val isStoreLocation: Boolean, + + @field:SerializedName("qris_image") + val qrisImage: String, + + @field:SerializedName("voucher_name") + val voucherName: Any, + + @field:SerializedName("payment_status") + val paymentStatus: String, + + @field:SerializedName("address_id") + val addressId: Int, + + @field:SerializedName("payment_amount") + val paymentAmount: String, + + @field:SerializedName("cancel_reason") + val cancelReason: String, + + @field:SerializedName("total_amount") + val totalAmount: String, + + @field:SerializedName("user_id") + val userId: Int, + + @field:SerializedName("province_id") + val provinceId: Int, + + @field:SerializedName("courier") + val courier: String, + + @field:SerializedName("subdistrict") + val subdistrict: String, + + @field:SerializedName("service") + val service: String, + + @field:SerializedName("pay_info_num") + val payInfoNum: String, + + @field:SerializedName("shipment_price") + val shipmentPrice: String, + + @field:SerializedName("voucher_id") + val voucherId: Any, + + @field:SerializedName("payment_info_id") + val paymentInfoId: Int, + + @field:SerializedName("detail") + val detail: String, + + @field:SerializedName("postal_code") + val postalCode: String, + + @field:SerializedName("order_id") + val orderId: Int, + + @field:SerializedName("city_id") + val cityId: Int +) + data class OrderItemsItem( + @field:SerializedName("order_item_id") + val orderItemId: Int, + @field:SerializedName("review_id") - val reviewId: Int? = null, + val reviewId: Int, @field:SerializedName("quantity") val quantity: Int, @@ -26,7 +140,10 @@ data class OrderItemsItem( val subtotal: Int, @field:SerializedName("product_image") - val productImage: String? = null, + val productImage: String, + + @field:SerializedName("product_id") + val productId: Int, @field:SerializedName("store_name") val storeName: String, @@ -37,93 +154,3 @@ data class OrderItemsItem( @field:SerializedName("product_name") val productName: String ) - -data class Orders( - - @field:SerializedName("receipt_num") - val receiptNum: String, - - @field:SerializedName("latitude") - val latitude: String, - - @field:SerializedName("created_at") - val createdAt: String, - - @field:SerializedName("voucher_code") - val voucherCode: String? = null, - - @field:SerializedName("updated_at") - val updatedAt: String, - - @field:SerializedName("etd") - val etd: String, - - @field:SerializedName("street") - val street: String, - - @field:SerializedName("cancel_date") - val cancelDate: String, - - @field:SerializedName("longitude") - val longitude: String, - - @field:SerializedName("shipment_status") - val shipmentStatus: String, - - @field:SerializedName("order_items") - val orderItems: List, - - @field:SerializedName("auto_completed_at") - val autoCompletedAt: String, - - @field:SerializedName("is_store_location") - val isStoreLocation: Boolean, - - @field:SerializedName("voucher_name") - val voucherName: String? = null, - - @field:SerializedName("address_id") - val addressId: Int, - - @field:SerializedName("payment_method_id") - val paymentMethodId: Int, - - @field:SerializedName("cancel_reason") - val cancelReason: String, - - @field:SerializedName("total_amount") - val totalAmount: String, - - @field:SerializedName("user_id") - val userId: Int, - - @field:SerializedName("province_id") - val provinceId: Int, - - @field:SerializedName("courier") - val courier: String, - - @field:SerializedName("subdistrict") - val subdistrict: String, - - @field:SerializedName("service") - val service: String, - - @field:SerializedName("shipment_price") - val shipmentPrice: String, - - @field:SerializedName("voucher_id") - val voucherId: Int? = null, - - @field:SerializedName("detail") - val detail: String, - - @field:SerializedName("postal_code") - val postalCode: String, - - @field:SerializedName("order_id") - val orderId: Int, - - @field:SerializedName("city_id") - val cityId: Int -) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt index 08df515..b02e934 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt @@ -13,6 +13,8 @@ data class DetailStoreProductResponse( data class PaymentInfoItem( + val id: Int = 1, + @field:SerializedName("qris_image") val qrisImage: String, diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt index d2aad68..b4bc29a 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt @@ -5,6 +5,7 @@ import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy +import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.response.cart.DataItem import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse @@ -181,18 +182,27 @@ class OrderRepository(private val apiService: ApiService) { } } - suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result { + suspend fun addAddress(request: CreateAddressRequest): Result { return try { - val response = apiService.createAddress(createAddressRequest) - if (response.isSuccessful){ - response.body()?.let { - Result.Success(it) - } ?: Result.Error(Exception("Add Address failed")) + Log.d("OrderRepository", "Adding address: $request") + val response = apiService.createAddress(request) + + if (response.isSuccessful) { + val createAddressResponse = response.body() + if (createAddressResponse != null) { + Log.d("OrderRepository", "Address added successfully: ${createAddressResponse.message}") + Result.Success(createAddressResponse) + } else { + Log.e("OrderRepository", "Response body was null") + Result.Error(Exception("Empty response from server")) + } } else { - Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}") - Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error")) + val errorBody = response.errorBody()?.string() ?: "Unknown error" + Log.e("OrderRepository", "Error adding address: $errorBody") + Result.Error(Exception(errorBody)) } } catch (e: Exception) { + Log.e("OrderRepository", "Exception adding address", e) Result.Error(e) } } @@ -207,4 +217,19 @@ class OrderRepository(private val apiService: ApiService) { return if (response.isSuccessful) response.body() else null } + suspend fun fetchUserProfile(): Result { + return try { + val response = apiService.getUserProfile() + if (response.isSuccessful) { + response.body()?.user?.let { + Result.Success(it) // ✅ Returning only UserProfile + } ?: Result.Error(Exception("User data not found")) + } else { + Result.Error(Exception("Error fetching profile: ${response.code()}")) + } + } catch (e: Exception) { + Result.Error(e) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt index 58d435b..f8c9b4c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt @@ -147,12 +147,18 @@ class CheckoutActivity : AppCompatActivity() { } private fun setupPaymentMethodsRecyclerView(paymentMethods: List) { - paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment -> - // Convert payment name to ID - val paymentId = payment.name.toIntOrNull() ?: 0 + if (paymentMethods.isEmpty()) { + Log.e("CheckoutActivity", "Payment methods list is empty") + Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show() + return + } - // Call the ViewModel's setPaymentMethod function - viewModel.setPaymentMethod(paymentId) + // Debug logging + Log.d("CheckoutActivity", "Setting up payment methods: ${paymentMethods.size} methods available") + + paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment -> + // We're using a hardcoded ID for now + viewModel.setPaymentMethod(1) } binding.rvPaymentMethods.apply { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt index 8d75cd3..30ca070 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt @@ -157,7 +157,19 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { val storeResult = repository.fetchStoreDetail(storeId) if (storeResult is Result.Success && storeResult.data != null) { - val paymentMethodsList = storeResult.data.paymentInfo + // For now, we'll use hardcoded payment ID (1) for all payment methods + // This will be updated once the backend provides proper IDs + val paymentMethodsList = storeResult.data.paymentInfo.map { paymentInfo -> + PaymentInfoItem( + id = 1, // Hardcoded payment ID + name = paymentInfo.name, + bankNum = paymentInfo.bankNum, + qrisImage = paymentInfo.qrisImage + ) + } + + Log.d(TAG, "Fetched ${paymentMethodsList.size} payment methods") + _availablePaymentMethods.value = paymentMethodsList callback(paymentMethodsList) } else { @@ -172,55 +184,47 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { } } - // Set payment method - // In CheckoutViewModel + // Updated setPaymentMethod function fun setPaymentMethod(paymentId: Int) { + // We'll use the hardcoded ID (1) for now + val currentPaymentId = 1 + viewModelScope.launch { try { - // Get the available payment methods, or fetch them if not available - val paymentMethods = _availablePaymentMethods.value ?: run { - val storeId = _checkoutData.value?.sellerId ?: return@launch - val storeResult = repository.fetchStoreDetail(storeId) - if (storeResult is Result.Success && storeResult.data != null) { - val methods = storeResult.data.paymentInfo - _availablePaymentMethods.value = methods - methods - } else { - emptyList() - } + // Get the available payment methods + val paymentMethods = _availablePaymentMethods.value + + if (paymentMethods.isNullOrEmpty()) { + // If no payment methods available, try to fetch them + getPaymentMethods { /* do nothing here */ } + return@launch } - // Find the selected payment method - val selectedPayment = paymentMethods.find { - // Change this line to match the actual way you want to identify the payment method - it.name == paymentId.toString() // to do: edit to using it.id - } + // Use the first payment method (or specific one if you prefer) + val selectedPayment = paymentMethods.first() - if (selectedPayment != null) { - // Set the selected payment - _selectedPayment.value = selectedPayment + // Set the selected payment + _selectedPayment.value = selectedPayment + Log.d(TAG, "Payment selected: Name=${selectedPayment.name}") - // Update the order request with the payment method ID - val currentData = _checkoutData.value ?: return@launch + // Update the order request with the payment method ID (hardcoded for now) + val currentData = _checkoutData.value ?: return@launch - // Different handling for Buy Now vs Cart checkout - if (currentData.isBuyNow) { - // For Buy Now checkout - val buyRequest = currentData.orderRequest as OrderRequestBuy - val updatedRequest = buyRequest.copy(paymentMethodId = paymentId) - _checkoutData.value = currentData.copy(orderRequest = updatedRequest) - } else { - // For Cart checkout - val cartRequest = currentData.orderRequest as OrderRequest - val updatedRequest = cartRequest.copy(paymentMethodId = paymentId) - _checkoutData.value = currentData.copy(orderRequest = updatedRequest) - } + // Different handling for Buy Now vs Cart checkout + if (currentData.isBuyNow) { + // For Buy Now checkout + val buyRequest = currentData.orderRequest as OrderRequestBuy + val updatedRequest = buyRequest.copy(paymentMethodId = currentPaymentId) + _checkoutData.value = currentData.copy(orderRequest = updatedRequest) } else { - // If no matching payment method is found - _errorMessage.value = "Selected payment method not found" + // For Cart checkout + val cartRequest = currentData.orderRequest as OrderRequest + val updatedRequest = cartRequest.copy(paymentMethodId = currentPaymentId) + _checkoutData.value = currentData.copy(orderRequest = updatedRequest) } } catch (e: Exception) { _errorMessage.value = "Error setting payment method: ${e.message}" + Log.e(TAG, "Error setting payment method", e) } } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt index 8621f0e..7748297 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt @@ -14,8 +14,8 @@ class PaymentMethodAdapter( private val onPaymentSelected: (PaymentInfoItem) -> Unit ) : RecyclerView.Adapter() { - // Track the selected position - private var selectedPosition = -1 + // Selected payment name + private var selectedPaymentName: String? = null class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) : RecyclerView.ViewHolder(binding.root) @@ -38,14 +38,23 @@ class PaymentMethodAdapter( // Set payment method name tvPaymentMethodName.text = payment.name - // Set radio button state - rbPaymentMethod.isChecked = selectedPosition == position +// // Set bank account number if available +// if (!payment.bankNum.isNullOrEmpty()) { +// tvPaymentAccountNumber.visibility = View.VISIBLE +// tvPaymentAccountNumber.text = payment.bankNum +// } else { +// tvPaymentAccountNumber.visibility = View.GONE +// } + + // Set radio button state based on selected payment name + rbPaymentMethod.isChecked = payment.name == selectedPaymentName // Load payment icon if available - if (payment.qrisImage.isNotEmpty()) { + if (!payment.qrisImage.isNullOrEmpty()) { Glide.with(ivPaymentMethod.context) .load(payment.qrisImage) - .apply(RequestOptions() + .apply( + RequestOptions() .placeholder(R.drawable.outline_store_24) .error(R.drawable.outline_store_24)) .into(ivPaymentMethod) @@ -56,35 +65,21 @@ class PaymentMethodAdapter( // Handle click on the entire item root.setOnClickListener { - selectPayment(position) onPaymentSelected(payment) + setSelectedPaymentName(payment.name) } // Handle click on the radio button rbPaymentMethod.setOnClickListener { - selectPayment(position) onPaymentSelected(payment) + setSelectedPaymentName(payment.name) } } } - // Helper method to handle payment selection - private fun selectPayment(position: Int) { - if (selectedPosition != position) { - val previousPosition = selectedPosition - selectedPosition = position - - // Update UI for previous and new selection - notifyItemChanged(previousPosition) - notifyItemChanged(position) - } - } - - //selected by name + // Set selected payment by name and refresh the UI fun setSelectedPaymentName(paymentName: String) { - val position = paymentMethods.indexOfFirst { it.name == paymentName } - if (position != -1 && position != selectedPosition) { - selectPayment(position) - } + selectedPaymentName = paymentName + notifyDataSetChanged() // Update all items to reflect selection change } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt index bc35d35..1d08e8f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt @@ -2,10 +2,11 @@ package com.alya.ecommerce_serang.ui.order import android.content.Intent import android.os.Bundle +import android.util.Log +import android.view.View import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.OrderRepository @@ -18,6 +19,7 @@ class ShippingActivity : AppCompatActivity() { private lateinit var binding: ActivityShippingBinding private lateinit var sessionManager: SessionManager private lateinit var shippingAdapter: ShippingAdapter + private val TAG = "ShippingActivity" private val viewModel: ShippingViewModel by viewModels { BaseViewModelFactory { @@ -40,8 +42,11 @@ class ShippingActivity : AppCompatActivity() { val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0) val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1) + Log.d(TAG, "Received data: addressId=$addressId, productId=$productId, quantity=$quantity") + // Validate required information if (addressId <= 0 || productId <= 0) { + Log.e(TAG, "Missing required shipping information: addressId=$addressId, productId=$productId") Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show() finish() return @@ -51,9 +56,10 @@ class ShippingActivity : AppCompatActivity() { setupToolbar() setupRecyclerView() setupObservers() + setupRetryButton() // Add a retry button for error cases // Load shipping options - viewModel.loadShippingOptions(addressId, productId, quantity) + loadShippingOptions(addressId, productId, quantity) } private fun setupToolbar() { @@ -65,6 +71,7 @@ class ShippingActivity : AppCompatActivity() { private fun setupRecyclerView() { shippingAdapter = ShippingAdapter { courierCostsItem, service -> // Handle shipping method selection + Log.d(TAG, "Selected shipping: ${courierCostsItem.courier} - ${service.service} - ${service.cost} - ${service.etd}") returnSelectedShipping( courierCostsItem.courier, service.service, @@ -79,29 +86,65 @@ class ShippingActivity : AppCompatActivity() { } } + private fun setupRetryButton() { + // If you have a retry button in your layout +// binding.btnRetry?.setOnClickListener { +// val addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, 0) +// val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0) +// val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1) +// loadShippingOptions(addressId, productId, quantity) +// } + } + + private fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) { + // Show loading state + binding.progressBar?.visibility = View.VISIBLE + binding.rvShipmentOrder.visibility = View.GONE +// binding.layoutEmptyShipping?.visibility = View.GONE + + // Load shipping options + Log.d(TAG, "Loading shipping options: addressId=$addressId, productId=$productId, quantity=$quantity") + viewModel.loadShippingOptions(addressId, productId, quantity) + } + private fun setupObservers() { // Observe shipping options viewModel.shippingOptions.observe(this) { courierOptions -> + Log.d(TAG, "Received ${courierOptions.size} shipping options") shippingAdapter.submitList(courierOptions) updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() }) } // Observe loading state viewModel.isLoading.observe(this) { isLoading -> -// binding.progressBar.isVisible = isLoading + binding.progressBar?.visibility = if (isLoading) View.VISIBLE else View.GONE + Log.d(TAG, "Loading state: $isLoading") } // Observe error messages viewModel.errorMessage.observe(this) { message -> if (message.isNotEmpty()) { + Log.e(TAG, "Error: $message") Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + + // Show error view if you have one +// binding.layoutError?.visibility = View.VISIBLE +// binding.tvErrorMessage?.text = message + } else { +// binding.layoutError?.visibility = View.GONE } } } private fun updateEmptyState(isEmpty: Boolean) { -// binding.layoutEmptyShipping.isVisible = isEmpty - binding.rvShipmentOrder.isVisible = !isEmpty + Log.d(TAG, "Updating empty state: isEmpty=$isEmpty") +// binding.layoutEmptyShipping?.visibility = if (isEmpty) View.VISIBLE else View.GONE + binding.rvShipmentOrder.visibility = if (isEmpty) View.GONE else View.VISIBLE + + // If empty, show appropriate message + if (isEmpty) { +// binding.tvEmptyMessage?.text = "No shipping options available for this address and product" + } } private fun returnSelectedShipping( @@ -116,11 +159,13 @@ class ShippingActivity : AppCompatActivity() { putExtra(EXTRA_SHIP_PRICE, shipPrice) putExtra(EXTRA_SHIP_ETD, shipEtd) } + Log.d(TAG, "Returning selected shipping: name=$shipName, service=$shipService, price=$shipPrice, etd=$shipEtd") setResult(RESULT_OK, intent) finish() } companion object { + // Constants for intent extras const val EXTRA_ADDRESS_ID = "extra_address_id" const val EXTRA_PRODUCT_ID = "extra_product_id" diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt index 17696c7..3ebf84a 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt @@ -36,12 +36,14 @@ class ShippingViewModel( _errorMessage.value = "" // Prepare the request + val costProduct = CostProduct( + productId = productId, + quantity = quantity + ) + val request = CourierCostRequest( addressId = addressId, - itemCost = CostProduct( - productId = productId, - quantity = quantity - ) + itemCost = listOf(costProduct) // Wrap in a list ) viewModelScope.launch { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt index ff9060e..6bf217c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt @@ -1,18 +1,22 @@ package com.alya.ecommerce_serang.ui.order.address import android.annotation.SuppressLint -import android.content.pm.PackageManager +import android.content.Intent +import android.location.Criteria import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.util.Log +import android.view.View import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.response.order.CitiesItem @@ -20,18 +24,20 @@ import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.repository.OrderRepository +import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager -import kotlinx.coroutines.launch class AddAddressActivity : AppCompatActivity() { private lateinit var binding: ActivityAddAddressBinding private lateinit var apiService: ApiService private lateinit var sessionManager: SessionManager - private lateinit var profileUser: UserProfile + private var profileUser: Int = 1 private lateinit var locationManager: LocationManager + private var isRequestingLocation = false + private var latitude: Double? = null private var longitude: Double? = null private val provinceAdapter by lazy { ProvinceAdapter(this) } @@ -41,7 +47,8 @@ class AddAddressActivity : AppCompatActivity() { SavedStateViewModelFactory(this) { savedStateHandle -> val apiService = ApiConfig.getApiService(sessionManager) val orderRepository = OrderRepository(apiService) - AddAddressViewModel(orderRepository, savedStateHandle) + val userRepository = UserRepository(apiService) + AddAddressViewModel(orderRepository, userRepository, savedStateHandle) } } @@ -54,45 +61,79 @@ class AddAddressActivity : AppCompatActivity() { apiService = ApiConfig.getApiService(sessionManager) locationManager = getSystemService(LOCATION_SERVICE) as LocationManager +// Get user profile from session manager +// profileUser =UserProfile. + viewModel.userProfile.observe(this){ user -> + user?.let { updateProfile(it) } + } + setupToolbar() + requestLocationPermission() + setupReloadButtons() setupAutoComplete() setupButtonListeners() - collectFlows() - requestLocationPermission() + setupObservers() + + // Force trigger province loading to ensure it happens + viewModel.getProvinces() + } + + private fun updateProfile(userProfile: UserProfile){ + profileUser = userProfile.userId } -// private fun viewModelAddAddress(request: CreateAddressRequest) { -// // Call the private fun in your ViewModel using reflection or expose it in ViewModel -// val method = AddAddressViewModel::class.java.getDeclaredMethod("addAddress", CreateAddressRequest::class.java) -// method.isAccessible = true -// method.invoke(viewModel, request) -// } // UI setup methods -private fun setupToolbar() { - binding.toolbar.setNavigationOnClickListener { - onBackPressedDispatcher.onBackPressed() + private fun setupToolbar() { + binding.toolbar.setNavigationOnClickListener { + onBackPressedDispatcher.onBackPressed() + } } -} private fun setupAutoComplete() { + Log.d(TAG, "Setting up AutoComplete dropdowns") // Set adapters binding.autoCompleteProvinsi.setAdapter(provinceAdapter) binding.autoCompleteKabupaten.setAdapter(cityAdapter) - // Set listeners - binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ -> - provinceAdapter.getProvinceId(position)?.let { provinceId -> - viewModel.getCities(provinceId) - binding.autoCompleteKabupaten.text.clear() + // Make dropdown appear on click (not just when typing) + binding.autoCompleteProvinsi.setOnClickListener { + Log.d(TAG, "Province dropdown clicked, showing dropdown") + binding.autoCompleteProvinsi.showDropDown() + } + + binding.autoCompleteKabupaten.setOnClickListener { + // Only show dropdown if we have cities loaded + if (cityAdapter.count > 0) { + Log.d(TAG, "City dropdown clicked, showing dropdown with ${cityAdapter.count} items") + binding.autoCompleteKabupaten.showDropDown() + } else { + Log.d(TAG, "City dropdown clicked but no cities available") + Toast.makeText(this, "Pilih provinsi terlebih dahulu", Toast.LENGTH_SHORT).show() } } + // Set listeners for selection + binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ -> + val provinceId = provinceAdapter.getProvinceId(position) + Log.d(TAG, "Province selected at position $position, provinceId=$provinceId") + + provinceId?.let { id -> + Log.d(TAG, "Getting cities for provinceId=$id") + viewModel.getCities(id) + binding.autoCompleteKabupaten.text.clear() + } ?: Log.e(TAG, "Could not get provinceId for position $position") + } + binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ -> - cityAdapter.getCityId(position)?.let { cityId -> - viewModel.selectedCityId = cityId - } + val cityId = cityAdapter.getCityId(position) + Log.d(TAG, "City selected at position $position, cityId=$cityId") + + cityId?.let { id -> + Log.d(TAG, "Setting selectedCityId=$id") + viewModel.selectedCityId = id + } ?: Log.e(TAG, "Could not get cityId for position $position") } } @@ -102,73 +143,93 @@ private fun setupToolbar() { } } - private fun collectFlows() { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.provincesState.collect { state -> - handleProvinceState(state) - } - } + private fun setupObservers() { + Log.d(TAG, "Setting up LiveData observers") - launch { - viewModel.citiesState.collect { state -> - handleCityState(state) - } - } + // Observe provinces + viewModel.provincesState.observe(this) { state -> + Log.d(TAG, "Received provincesState update: $state") + handleProvinceState(state) + } - launch { - viewModel.addressSubmissionState.collect { state -> - handleAddressSubmissionState(state) - } - } - } + // Observe cities + viewModel.citiesState.observe(this) { state -> + Log.d(TAG, "Received citiesState update: $state") + handleCityState(state) + } + + // Observe address submission + viewModel.addressSubmissionState.observe(this) { state -> + Log.d(TAG, "Received addressSubmissionState update: $state") + handleAddressSubmissionState(state) } } private fun handleProvinceState(state: ViewState>) { when (state) { - is ViewState.Loading -> null //showProvinceLoading(true) + is ViewState.Loading -> { + Log.d("AddAddressActivity", "Loading provinces...") + // Show loading indicator + } is ViewState.Success -> { - provinceAdapter.updateData(state.data) + Log.d("AddAddressActivity", "Provinces loaded: ${state.data.size}") + // Hide loading indicator + if (state.data.isNotEmpty()) { + provinceAdapter.updateData(state.data) + } else { + showError("No provinces available") + } } is ViewState.Error -> { - showError(state.message) + // Hide loading indicator + showError("Failed to load provinces: ${state.message}") + Log.e("AddAddressActivity", "Province error: ${state.message}") } } } private fun handleCityState(state: ViewState>) { when (state) { - is ViewState.Loading -> null //showCityLoading(true) + is ViewState.Loading -> { + Log.d("AddAddressActivity", "Loading cities...") + binding.cityProgressBar.visibility = View.VISIBLE + } is ViewState.Success -> { -// showCityLoading(false) + Log.d("AddAddressActivity", "Cities loaded: ${state.data.size}") + binding.cityProgressBar.visibility = View.GONE cityAdapter.updateData(state.data) } is ViewState.Error -> { -// showCityLoading(false) - showError(state.message) + binding.cityProgressBar.visibility = View.GONE + showError("Failed to load cities: ${state.message}") + Log.e("AddAddressActivity", "City error: ${state.message}") } } } private fun handleAddressSubmissionState(state: ViewState) { when (state) { - is ViewState.Loading -> null //showSubmitLoading(true) + is ViewState.Loading -> { + Log.d(TAG, "Address submission: Loading") + showSubmitLoading(true) + } is ViewState.Success -> { + Log.d(TAG, "Address submission: Success - ${state.data}") showSubmitLoading(false) showSuccessAndFinish(state.data) } is ViewState.Error -> { + Log.e(TAG, "Address submission: Error - ${state.message}") showSubmitLoading(false) showError(state.message) } } } + private fun showSubmitLoading(isLoading: Boolean) { binding.buttonSimpan.isEnabled = !isLoading binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan" - // You might want to show a progress bar as well +// binding.submitProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE } private fun showError(message: String) { @@ -177,47 +238,83 @@ private fun setupToolbar() { private fun showSuccessAndFinish(message: String) { Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show() - onBackPressed() + setResult(RESULT_OK) + finish() } private fun validateAndSubmitForm() { - val lat = latitude - val long = longitude + Log.d(TAG, "Validating form...") + Log.d(TAG, "Current location: lat=$latitude, long=$longitude") - if (lat == null || long == null) { - showError("Lokasi belum terdeteksi") - return + // Check if we have location - always use default if not available + if (latitude == null || longitude == null) { + Log.w(TAG, "No location detected, using default location") + // Default location for Jakarta + latitude = -6.200000 + longitude = 106.816666 + binding.tvLocationStatus.text = "Menggunakan lokasi default: Jakarta" } - val street = binding.etDetailAlamat.text.toString() - val subDistrict = binding.etKecamatan.text.toString() - val postalCode = binding.etKodePos.text.toString() - val recipient = binding.etNamaPenerima.text.toString() - val phone = binding.etNomorHp.text.toString() - val userId = profileUser.userId + val street = binding.etDetailAlamat.text.toString().trim() + val subDistrict = binding.etKecamatan.text.toString().trim() + val postalCode = binding.etKodePos.text.toString().trim() + val recipient = binding.etNamaPenerima.text.toString().trim() + val phone = binding.etNomorHp.text.toString().trim() + val userId = try { + profileUser + } catch (e: Exception) { + Log.w(TAG, "Error getting userId, using default", e) + 1 // Default userId for testing + } val isStoreLocation = false val provinceId = viewModel.selectedProvinceId val cityId = viewModel.selectedCityId - if (street.isBlank() || recipient.isBlank() || phone.isBlank()) { - showError("Lengkapi semua field wajib") + Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " + + "recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, " + + "lat=$latitude, long=$longitude") + + // Validate required fields + if (street.isBlank()) { + Log.w(TAG, "Validation failed: street is blank") + binding.etDetailAlamat.error = "Alamat tidak boleh kosong" + binding.etDetailAlamat.requestFocus() + return + } + + if (recipient.isBlank()) { + Log.w(TAG, "Validation failed: recipient is blank") + binding.etNamaPenerima.error = "Nama penerima tidak boleh kosong" + binding.etNamaPenerima.requestFocus() + return + } + + if (phone.isBlank()) { + Log.w(TAG, "Validation failed: phone is blank") + binding.etNomorHp.error = "Nomor HP tidak boleh kosong" + binding.etNomorHp.requestFocus() return } if (provinceId == null) { + Log.w(TAG, "Validation failed: provinceId is null") showError("Pilih provinsi terlebih dahulu") + binding.autoCompleteProvinsi.requestFocus() return } if (cityId == null) { + Log.w(TAG, "Validation failed: cityId is null") showError("Pilih kota/kabupaten terlebih dahulu") + binding.autoCompleteKabupaten.requestFocus() return } + // Create request with all fields val request = CreateAddressRequest( - lat = lat, - long = long, + lat = latitude!!, // Safe to use !! as we've checked above + long = longitude!!, street = street, subDistrict = subDistrict, cityId = cityId, @@ -230,12 +327,17 @@ private fun setupToolbar() { isStoreLocation = isStoreLocation ) + Log.d(TAG, "Form validation successful, submitting address: $request") viewModel.addAddress(request) } private val locationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - if (granted) requestLocation() else Toast.makeText(this, "Izin lokasi ditolak",Toast.LENGTH_SHORT).show() + if (granted) { + requestLocation() + } else { + Toast.makeText(this, "Izin lokasi ditolak", Toast.LENGTH_SHORT).show() + } } private fun requestLocationPermission() { @@ -244,36 +346,164 @@ private fun setupToolbar() { @SuppressLint("MissingPermission") private fun requestLocation() { - val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) - val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + Log.d(TAG, "Requesting device location") - if (!isGpsEnabled && !isNetworkEnabled) { - Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show() + // Check if we're already requesting location to avoid multiple requests + if (isRequestingLocation) { + Log.w(TAG, "Location request already in progress") return } - val provider = if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER + isRequestingLocation = true + binding.locationProgressBar.visibility = View.VISIBLE + binding.tvLocationStatus.text = "Mencari lokasi..." - locationManager.requestSingleUpdate(provider, object : LocationListener { + val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + + Log.d(TAG, "Location providers: GPS=$isGpsEnabled, Network=$isNetworkEnabled") + + if (!isGpsEnabled && !isNetworkEnabled) { + Log.w(TAG, "No location providers enabled") + binding.locationProgressBar.visibility = View.GONE + binding.tvLocationStatus.text = "Provider lokasi tidak tersedia" + isRequestingLocation = false + Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show() + showEnableLocationDialog() + return + } + + // Create location criteria + val criteria = Criteria() + criteria.accuracy = Criteria.ACCURACY_FINE + criteria.isBearingRequired = false + criteria.isAltitudeRequired = false + criteria.isSpeedRequired = false + criteria.powerRequirement = Criteria.POWER_LOW + + // Get the best provider based on criteria + val provider = locationManager.getBestProvider(criteria, true) ?: + if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER + + Log.d(TAG, "Using location provider: $provider") + + // Set timeout for location + Handler(Looper.getMainLooper()).postDelayed({ + if (isRequestingLocation) { + Log.w(TAG, "Location timeout, using default") + binding.locationProgressBar.visibility = View.GONE + binding.tvLocationStatus.text = "Lokasi default: Jakarta" + latitude = -6.200000 + longitude = 106.816666 + isRequestingLocation = false + Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show() + } + }, 15000) // 15 seconds timeout + + // Try getting last known location first + try { + val lastLocation = locationManager.getLastKnownLocation(provider) + if (lastLocation != null) { + Log.d(TAG, "Using last known location") + latitude = lastLocation.latitude + longitude = lastLocation.longitude + binding.locationProgressBar.visibility = View.GONE + binding.tvLocationStatus.text = "Lokasi terdeteksi: ${lastLocation.latitude}, ${lastLocation.longitude}" + isRequestingLocation = false + Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() + return + } else { + Log.d(TAG, "No last known location, requesting updates") + } + } catch (e: Exception) { + Log.e(TAG, "Error getting last known location", e) + } + + // Create a location listener + val locationListener = object : LocationListener { override fun onLocationChanged(location: Location) { + Log.d(TAG, "onLocationChanged called: lat=${location.latitude}, long=${location.longitude}") latitude = location.latitude longitude = location.longitude + binding.locationProgressBar.visibility = View.GONE + binding.tvLocationStatus.text = "Lokasi terdeteksi: ${location.latitude}, ${location.longitude}" + isRequestingLocation = false + Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() + + // Remove location updates after receiving a location + try { + locationManager.removeUpdates(this) + } catch (e: Exception) { + Log.e(TAG, "Error removing location updates", e) + } + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Log.d(TAG, "Location provider status changed: provider=$provider, status=$status") + } + + override fun onProviderEnabled(provider: String) { + Log.d(TAG, "Location provider enabled: $provider") } - override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} - override fun onProviderEnabled(provider: String) {} override fun onProviderDisabled(provider: String) { - Toast.makeText(this@AddAddressActivity, "Provider dimatikan", Toast.LENGTH_SHORT).show() + Log.w(TAG, "Location provider disabled: $provider") + binding.locationProgressBar.visibility = View.GONE + binding.tvLocationStatus.text = "Provider lokasi dimatikan" + isRequestingLocation = false + Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show() } - }, null) - } + } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - requestLocation() - } else { - Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show() + try { + // Request location updates + Log.d(TAG, "Requesting location updates from $provider") + locationManager.requestLocationUpdates( + provider, + 0, // minimum time interval between updates (in milliseconds) + 0f, // minimum distance between updates (in meters) + locationListener, + Looper.getMainLooper() + ) + } catch (e: Exception) { + Log.e(TAG, "Exception requesting location update", e) + binding.locationProgressBar.visibility = View.GONE + binding.tvLocationStatus.text = "Error: ${e.message}" + isRequestingLocation = false + Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show() + + // Set default location + latitude = -6.200000 + longitude = 106.816666 } } + + private fun showEnableLocationDialog() { + AlertDialog.Builder(this) + .setTitle("Aktifkan Lokasi") + .setMessage("Aplikasi memerlukan akses lokasi. Silakan aktifkan lokasi di pengaturan.") + .setPositiveButton("Pengaturan") { _, _ -> + startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) + } + .setNegativeButton("Batal") { dialog, _ -> + dialog.dismiss() + } + .create() + .show() + } + + private fun setupReloadButtons() { + // Add button to reload provinces (add this button to your layout) + + // Add button to reload location (add this button to your layout) + binding.btnReloadLocation.setOnClickListener { + Log.d(TAG, "Reload location button clicked") + Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show() + requestLocation() + } + } + + companion object { + private const val TAG = "AddAddressViewModel" + } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt index fd462d2..085d12c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt @@ -1,28 +1,35 @@ package com.alya.ecommerce_serang.ui.order.address import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest +import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.response.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.Result -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import com.alya.ecommerce_serang.data.repository.UserRepository import kotlinx.coroutines.launch -class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() { - // Flow states for data - private val _addressSubmissionState = MutableStateFlow>(ViewState.Loading) - val addressSubmissionState = _addressSubmissionState.asStateFlow() +class AddAddressViewModel(private val repository: OrderRepository, private val userRepo: UserRepository, private val savedStateHandle: SavedStateHandle): ViewModel() { + private val _addressSubmissionState = MutableLiveData>() + val addressSubmissionState: LiveData> = _addressSubmissionState - private val _provincesState = MutableStateFlow>>(ViewState.Loading) - val provincesState = _provincesState.asStateFlow() + private val _userProfile = MutableLiveData() + val userProfile: LiveData = _userProfile - private val _citiesState = MutableStateFlow>>(ViewState.Loading) - val citiesState = _citiesState.asStateFlow() + private val _errorMessageUser = MutableLiveData() + val errorMessageUser : LiveData = _errorMessageUser + + private val _provincesState = MutableLiveData>>() + val provincesState: LiveData>> = _provincesState + + private val _citiesState = MutableLiveData>>() + val citiesState: LiveData>> = _citiesState // Stored in SavedStateHandle for configuration changes var selectedProvinceId: Int? @@ -38,47 +45,82 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s getProvinces() } - fun addAddress(request: CreateAddressRequest){ + fun addAddress(request: CreateAddressRequest) { + Log.d(TAG, "Starting address submission process") + _addressSubmissionState.value = ViewState.Loading viewModelScope.launch { - when (val result = repository.addAddress(request)) { - is Result.Success -> { - val message = result.data.message // Ambil `message` dari CreateAddressResponse - _addressSubmissionState.value = ViewState.Success(message) - } - is Result.Error -> { - _addressSubmissionState.value = - ViewState.Error(result.exception.message ?: "Unknown error") - } - is Result.Loading -> { - // Optional, karena sudah set Loading di awal + try { + Log.d(TAG, "Calling repository.addAddress with request: $request") + val result = repository.addAddress(request) + + when (result) { + is Result.Success -> { + val message = result.data.message + Log.d(TAG, "Address added successfully: $message") + _addressSubmissionState.postValue(ViewState.Success(message)) + } + is Result.Error -> { + val errorMsg = result.exception.message ?: "Unknown error" + Log.e(TAG, "Error from repository: $errorMsg", result.exception) + _addressSubmissionState.postValue(ViewState.Error(errorMsg)) + } + is Result.Loading -> { + Log.d(TAG, "Repository returned Loading state") + // We already set Loading at the beginning + } + else -> { + Log.e(TAG, "Repository returned unexpected result type: $result") + _addressSubmissionState.postValue(ViewState.Error("Unexpected error occurred")) + } } + } catch (e: Exception) { + Log.e(TAG, "Exception occurred during address submission", e) + val errorMessage = e.message ?: "Unknown error occurred" + Log.e(TAG, "Error message: $errorMessage") + + // Log the exception stack trace + e.printStackTrace() + + _addressSubmissionState.postValue(ViewState.Error(errorMessage)) } } } - fun getProvinces(){ + fun getProvinces() { + _provincesState.value = ViewState.Loading viewModelScope.launch { try { val result = repository.getListProvinces() - result?.let { - _provincesState.value = ViewState.Success(it.provinces) + if (result?.provinces != null) { + _provincesState.postValue(ViewState.Success(result.provinces)) + Log.d(TAG, "Provinces loaded: ${result.provinces.size}") + } else { + _provincesState.postValue(ViewState.Error("Failed to load provinces")) + Log.e(TAG, "Province result was null or empty") } } catch (e: Exception) { - Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}") + _provincesState.postValue(ViewState.Error(e.message ?: "Error loading provinces")) + Log.e(TAG, "Error fetching provinces", e) } } } fun getCities(provinceId: Int){ + _citiesState.value = ViewState.Loading viewModelScope.launch { try { selectedProvinceId = provinceId val result = repository.getListCities(provinceId) result?.let { - _citiesState.value = ViewState.Success(it.cities) + _citiesState.postValue(ViewState.Success(it.cities)) + Log.d(TAG, "Cities loaded for province $provinceId: ${it.cities.size}") + } ?: run { + _citiesState.postValue(ViewState.Error("Failed to load cities")) + Log.e(TAG, "City result was null for province $provinceId") } } catch (e: Exception) { - Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}") + _citiesState.postValue(ViewState.Error(e.message ?: "Error loading cities")) + Log.e(TAG, "Error fetching cities for province $provinceId", e) } } } @@ -91,6 +133,16 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s selectedCityId = id } + fun loadUserProfile(){ + viewModelScope.launch { + when (val result = userRepo.fetchUserProfile()){ + is Result.Success -> _userProfile.postValue(result.data) + is Result.Error -> _errorMessageUser.postValue(result.exception.message ?: "Unknown Error") + is Result.Loading -> null + } + } + } + companion object { private const val TAG = "AddAddressViewModel" } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt index 44f7c29..ba194a7 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt @@ -1,6 +1,7 @@ package com.alya.ecommerce_serang.ui.order.address import android.content.Context +import android.util.Log import android.widget.ArrayAdapter import com.alya.ecommerce_serang.data.api.response.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem @@ -20,6 +21,8 @@ class ProvinceAdapter( clear() addAll(provinces.map { it.province }) notifyDataSetChanged() + + Log.d("ProvinceAdapter", "Updated with ${provinces.size} provinces") } fun getProvinceId(position: Int): Int? { diff --git a/app/src/main/res/layout/activity_add_address.xml b/app/src/main/res/layout/activity_add_address.xml index b5acc12..986d62d 100644 --- a/app/src/main/res/layout/activity_add_address.xml +++ b/app/src/main/res/layout/activity_add_address.xml @@ -115,10 +115,21 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="none" + android:focusable="false" + android:clickable="true" android:padding="12dp" android:textSize="14sp" /> + + + + + + +