mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
fix bgt order (-paymentId)
This commit is contained in:
@ -7,7 +7,7 @@ data class CourierCostRequest(
|
|||||||
val addressId: Int,
|
val addressId: Int,
|
||||||
|
|
||||||
@SerializedName("items")
|
@SerializedName("items")
|
||||||
val itemCost: CostProduct
|
val itemCost: List<CostProduct>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CostProduct (
|
data class CostProduct (
|
||||||
|
@ -4,17 +4,131 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
data class OrderDetailResponse(
|
data class OrderDetailResponse(
|
||||||
|
|
||||||
@field:SerializedName("orders")
|
@field:SerializedName("orders")
|
||||||
val orders: Orders,
|
val orders: Orders,
|
||||||
|
|
||||||
@field:SerializedName("message")
|
@field:SerializedName("message")
|
||||||
val message: String
|
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<OrderItemsItem>,
|
||||||
|
|
||||||
|
@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(
|
data class OrderItemsItem(
|
||||||
|
|
||||||
|
@field:SerializedName("order_item_id")
|
||||||
|
val orderItemId: Int,
|
||||||
|
|
||||||
@field:SerializedName("review_id")
|
@field:SerializedName("review_id")
|
||||||
val reviewId: Int? = null,
|
val reviewId: Int,
|
||||||
|
|
||||||
@field:SerializedName("quantity")
|
@field:SerializedName("quantity")
|
||||||
val quantity: Int,
|
val quantity: Int,
|
||||||
@ -26,7 +140,10 @@ data class OrderItemsItem(
|
|||||||
val subtotal: Int,
|
val subtotal: Int,
|
||||||
|
|
||||||
@field:SerializedName("product_image")
|
@field:SerializedName("product_image")
|
||||||
val productImage: String? = null,
|
val productImage: String,
|
||||||
|
|
||||||
|
@field:SerializedName("product_id")
|
||||||
|
val productId: Int,
|
||||||
|
|
||||||
@field:SerializedName("store_name")
|
@field:SerializedName("store_name")
|
||||||
val storeName: String,
|
val storeName: String,
|
||||||
@ -37,93 +154,3 @@ data class OrderItemsItem(
|
|||||||
@field:SerializedName("product_name")
|
@field:SerializedName("product_name")
|
||||||
val productName: String
|
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<OrderItemsItem>,
|
|
||||||
|
|
||||||
@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
|
|
||||||
)
|
|
||||||
|
@ -13,6 +13,8 @@ data class DetailStoreProductResponse(
|
|||||||
|
|
||||||
data class PaymentInfoItem(
|
data class PaymentInfoItem(
|
||||||
|
|
||||||
|
val id: Int = 1,
|
||||||
|
|
||||||
@field:SerializedName("qris_image")
|
@field:SerializedName("qris_image")
|
||||||
val qrisImage: String,
|
val qrisImage: String,
|
||||||
|
|
||||||
|
@ -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.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
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.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.cart.DataItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
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<CreateAddressResponse> {
|
suspend fun addAddress(request: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = apiService.createAddress(createAddressRequest)
|
Log.d("OrderRepository", "Adding address: $request")
|
||||||
if (response.isSuccessful){
|
val response = apiService.createAddress(request)
|
||||||
response.body()?.let {
|
|
||||||
Result.Success(it)
|
if (response.isSuccessful) {
|
||||||
} ?: Result.Error(Exception("Add Address failed"))
|
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 {
|
} else {
|
||||||
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
|
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||||
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
|
Log.e("OrderRepository", "Error adding address: $errorBody")
|
||||||
|
Result.Error(Exception(errorBody))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderRepository", "Exception adding address", e)
|
||||||
Result.Error(e)
|
Result.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,4 +217,19 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
return if (response.isSuccessful) response.body() else null
|
return if (response.isSuccessful) response.body() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun fetchUserProfile(): Result<UserProfile?> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -147,12 +147,18 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentInfoItem>) {
|
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentInfoItem>) {
|
||||||
paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment ->
|
if (paymentMethods.isEmpty()) {
|
||||||
// Convert payment name to ID
|
Log.e("CheckoutActivity", "Payment methods list is empty")
|
||||||
val paymentId = payment.name.toIntOrNull() ?: 0
|
Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Call the ViewModel's setPaymentMethod function
|
// Debug logging
|
||||||
viewModel.setPaymentMethod(paymentId)
|
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 {
|
binding.rvPaymentMethods.apply {
|
||||||
|
@ -157,7 +157,19 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
val storeResult = repository.fetchStoreDetail(storeId)
|
val storeResult = repository.fetchStoreDetail(storeId)
|
||||||
|
|
||||||
if (storeResult is Result.Success && storeResult.data != null) {
|
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
|
_availablePaymentMethods.value = paymentMethodsList
|
||||||
callback(paymentMethodsList)
|
callback(paymentMethodsList)
|
||||||
} else {
|
} else {
|
||||||
@ -172,55 +184,47 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set payment method
|
// Updated setPaymentMethod function
|
||||||
// In CheckoutViewModel
|
|
||||||
fun setPaymentMethod(paymentId: Int) {
|
fun setPaymentMethod(paymentId: Int) {
|
||||||
|
// We'll use the hardcoded ID (1) for now
|
||||||
|
val currentPaymentId = 1
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
// Get the available payment methods, or fetch them if not available
|
// Get the available payment methods
|
||||||
val paymentMethods = _availablePaymentMethods.value ?: run {
|
val paymentMethods = _availablePaymentMethods.value
|
||||||
val storeId = _checkoutData.value?.sellerId ?: return@launch
|
|
||||||
val storeResult = repository.fetchStoreDetail(storeId)
|
if (paymentMethods.isNullOrEmpty()) {
|
||||||
if (storeResult is Result.Success && storeResult.data != null) {
|
// If no payment methods available, try to fetch them
|
||||||
val methods = storeResult.data.paymentInfo
|
getPaymentMethods { /* do nothing here */ }
|
||||||
_availablePaymentMethods.value = methods
|
return@launch
|
||||||
methods
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the selected payment method
|
// Use the first payment method (or specific one if you prefer)
|
||||||
val selectedPayment = paymentMethods.find {
|
val selectedPayment = paymentMethods.first()
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedPayment != null) {
|
// Set the selected payment
|
||||||
// Set the selected payment
|
_selectedPayment.value = selectedPayment
|
||||||
_selectedPayment.value = selectedPayment
|
Log.d(TAG, "Payment selected: Name=${selectedPayment.name}")
|
||||||
|
|
||||||
// Update the order request with the payment method ID
|
// Update the order request with the payment method ID (hardcoded for now)
|
||||||
val currentData = _checkoutData.value ?: return@launch
|
val currentData = _checkoutData.value ?: return@launch
|
||||||
|
|
||||||
// Different handling for Buy Now vs Cart checkout
|
// Different handling for Buy Now vs Cart checkout
|
||||||
if (currentData.isBuyNow) {
|
if (currentData.isBuyNow) {
|
||||||
// For Buy Now checkout
|
// For Buy Now checkout
|
||||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||||
val updatedRequest = buyRequest.copy(paymentMethodId = paymentId)
|
val updatedRequest = buyRequest.copy(paymentMethodId = currentPaymentId)
|
||||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
_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)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// If no matching payment method is found
|
// For Cart checkout
|
||||||
_errorMessage.value = "Selected payment method not found"
|
val cartRequest = currentData.orderRequest as OrderRequest
|
||||||
|
val updatedRequest = cartRequest.copy(paymentMethodId = currentPaymentId)
|
||||||
|
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_errorMessage.value = "Error setting payment method: ${e.message}"
|
_errorMessage.value = "Error setting payment method: ${e.message}"
|
||||||
|
Log.e(TAG, "Error setting payment method", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ class PaymentMethodAdapter(
|
|||||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
||||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||||
|
|
||||||
// Track the selected position
|
// Selected payment name
|
||||||
private var selectedPosition = -1
|
private var selectedPaymentName: String? = null
|
||||||
|
|
||||||
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
@ -38,14 +38,23 @@ class PaymentMethodAdapter(
|
|||||||
// Set payment method name
|
// Set payment method name
|
||||||
tvPaymentMethodName.text = payment.name
|
tvPaymentMethodName.text = payment.name
|
||||||
|
|
||||||
// Set radio button state
|
// // Set bank account number if available
|
||||||
rbPaymentMethod.isChecked = selectedPosition == position
|
// 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
|
// Load payment icon if available
|
||||||
if (payment.qrisImage.isNotEmpty()) {
|
if (!payment.qrisImage.isNullOrEmpty()) {
|
||||||
Glide.with(ivPaymentMethod.context)
|
Glide.with(ivPaymentMethod.context)
|
||||||
.load(payment.qrisImage)
|
.load(payment.qrisImage)
|
||||||
.apply(RequestOptions()
|
.apply(
|
||||||
|
RequestOptions()
|
||||||
.placeholder(R.drawable.outline_store_24)
|
.placeholder(R.drawable.outline_store_24)
|
||||||
.error(R.drawable.outline_store_24))
|
.error(R.drawable.outline_store_24))
|
||||||
.into(ivPaymentMethod)
|
.into(ivPaymentMethod)
|
||||||
@ -56,35 +65,21 @@ class PaymentMethodAdapter(
|
|||||||
|
|
||||||
// Handle click on the entire item
|
// Handle click on the entire item
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
selectPayment(position)
|
|
||||||
onPaymentSelected(payment)
|
onPaymentSelected(payment)
|
||||||
|
setSelectedPaymentName(payment.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle click on the radio button
|
// Handle click on the radio button
|
||||||
rbPaymentMethod.setOnClickListener {
|
rbPaymentMethod.setOnClickListener {
|
||||||
selectPayment(position)
|
|
||||||
onPaymentSelected(payment)
|
onPaymentSelected(payment)
|
||||||
|
setSelectedPaymentName(payment.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to handle payment selection
|
// Set selected payment by name and refresh the UI
|
||||||
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
|
|
||||||
fun setSelectedPaymentName(paymentName: String) {
|
fun setSelectedPaymentName(paymentName: String) {
|
||||||
val position = paymentMethods.indexOfFirst { it.name == paymentName }
|
selectedPaymentName = paymentName
|
||||||
if (position != -1 && position != selectedPosition) {
|
notifyDataSetChanged() // Update all items to reflect selection change
|
||||||
selectPayment(position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,10 +2,11 @@ package com.alya.ecommerce_serang.ui.order
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
@ -18,6 +19,7 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityShippingBinding
|
private lateinit var binding: ActivityShippingBinding
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
private lateinit var shippingAdapter: ShippingAdapter
|
private lateinit var shippingAdapter: ShippingAdapter
|
||||||
|
private val TAG = "ShippingActivity"
|
||||||
|
|
||||||
private val viewModel: ShippingViewModel by viewModels {
|
private val viewModel: ShippingViewModel by viewModels {
|
||||||
BaseViewModelFactory {
|
BaseViewModelFactory {
|
||||||
@ -40,8 +42,11 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
||||||
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
||||||
|
|
||||||
|
Log.d(TAG, "Received data: addressId=$addressId, productId=$productId, quantity=$quantity")
|
||||||
|
|
||||||
// Validate required information
|
// Validate required information
|
||||||
if (addressId <= 0 || productId <= 0) {
|
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()
|
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show()
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
@ -51,9 +56,10 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
setupToolbar()
|
setupToolbar()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
|
setupRetryButton() // Add a retry button for error cases
|
||||||
|
|
||||||
// Load shipping options
|
// Load shipping options
|
||||||
viewModel.loadShippingOptions(addressId, productId, quantity)
|
loadShippingOptions(addressId, productId, quantity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
@ -65,6 +71,7 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
||||||
// Handle shipping method selection
|
// Handle shipping method selection
|
||||||
|
Log.d(TAG, "Selected shipping: ${courierCostsItem.courier} - ${service.service} - ${service.cost} - ${service.etd}")
|
||||||
returnSelectedShipping(
|
returnSelectedShipping(
|
||||||
courierCostsItem.courier,
|
courierCostsItem.courier,
|
||||||
service.service,
|
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() {
|
private fun setupObservers() {
|
||||||
// Observe shipping options
|
// Observe shipping options
|
||||||
viewModel.shippingOptions.observe(this) { courierOptions ->
|
viewModel.shippingOptions.observe(this) { courierOptions ->
|
||||||
|
Log.d(TAG, "Received ${courierOptions.size} shipping options")
|
||||||
shippingAdapter.submitList(courierOptions)
|
shippingAdapter.submitList(courierOptions)
|
||||||
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe loading state
|
// Observe loading state
|
||||||
viewModel.isLoading.observe(this) { isLoading ->
|
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
|
// Observe error messages
|
||||||
viewModel.errorMessage.observe(this) { message ->
|
viewModel.errorMessage.observe(this) { message ->
|
||||||
if (message.isNotEmpty()) {
|
if (message.isNotEmpty()) {
|
||||||
|
Log.e(TAG, "Error: $message")
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
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) {
|
private fun updateEmptyState(isEmpty: Boolean) {
|
||||||
// binding.layoutEmptyShipping.isVisible = isEmpty
|
Log.d(TAG, "Updating empty state: isEmpty=$isEmpty")
|
||||||
binding.rvShipmentOrder.isVisible = !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(
|
private fun returnSelectedShipping(
|
||||||
@ -116,11 +159,13 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
||||||
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Returning selected shipping: name=$shipName, service=$shipService, price=$shipPrice, etd=$shipEtd")
|
||||||
setResult(RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
// Constants for intent extras
|
// Constants for intent extras
|
||||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||||
|
@ -36,12 +36,14 @@ class ShippingViewModel(
|
|||||||
_errorMessage.value = ""
|
_errorMessage.value = ""
|
||||||
|
|
||||||
// Prepare the request
|
// Prepare the request
|
||||||
|
val costProduct = CostProduct(
|
||||||
|
productId = productId,
|
||||||
|
quantity = quantity
|
||||||
|
)
|
||||||
|
|
||||||
val request = CourierCostRequest(
|
val request = CourierCostRequest(
|
||||||
addressId = addressId,
|
addressId = addressId,
|
||||||
itemCost = CostProduct(
|
itemCost = listOf(costProduct) // Wrap in a list
|
||||||
productId = productId,
|
|
||||||
quantity = quantity
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.address
|
package com.alya.ecommerce_serang.ui.order.address
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.PackageManager
|
import android.content.Intent
|
||||||
|
import android.location.Criteria
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
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 android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
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.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.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
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.databinding.ActivityAddAddressBinding
|
||||||
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
|
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AddAddressActivity : AppCompatActivity() {
|
class AddAddressActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityAddAddressBinding
|
private lateinit var binding: ActivityAddAddressBinding
|
||||||
private lateinit var apiService: ApiService
|
private lateinit var apiService: ApiService
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
private lateinit var profileUser: UserProfile
|
private var profileUser: Int = 1
|
||||||
private lateinit var locationManager: LocationManager
|
private lateinit var locationManager: LocationManager
|
||||||
|
|
||||||
|
private var isRequestingLocation = false
|
||||||
|
|
||||||
private var latitude: Double? = null
|
private var latitude: Double? = null
|
||||||
private var longitude: Double? = null
|
private var longitude: Double? = null
|
||||||
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
||||||
@ -41,7 +47,8 @@ class AddAddressActivity : AppCompatActivity() {
|
|||||||
SavedStateViewModelFactory(this) { savedStateHandle ->
|
SavedStateViewModelFactory(this) { savedStateHandle ->
|
||||||
val apiService = ApiConfig.getApiService(sessionManager)
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
val orderRepository = OrderRepository(apiService)
|
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)
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||||
|
|
||||||
|
// Get user profile from session manager
|
||||||
|
// profileUser =UserProfile.
|
||||||
|
viewModel.userProfile.observe(this){ user ->
|
||||||
|
user?.let { updateProfile(it) }
|
||||||
|
}
|
||||||
|
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
|
requestLocationPermission()
|
||||||
|
setupReloadButtons()
|
||||||
setupAutoComplete()
|
setupAutoComplete()
|
||||||
setupButtonListeners()
|
setupButtonListeners()
|
||||||
collectFlows()
|
setupObservers()
|
||||||
requestLocationPermission()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
// UI setup methods
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
binding.toolbar.setNavigationOnClickListener {
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupAutoComplete() {
|
private fun setupAutoComplete() {
|
||||||
|
Log.d(TAG, "Setting up AutoComplete dropdowns")
|
||||||
// Set adapters
|
// Set adapters
|
||||||
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
||||||
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
||||||
|
|
||||||
// Set listeners
|
// Make dropdown appear on click (not just when typing)
|
||||||
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
binding.autoCompleteProvinsi.setOnClickListener {
|
||||||
provinceAdapter.getProvinceId(position)?.let { provinceId ->
|
Log.d(TAG, "Province dropdown clicked, showing dropdown")
|
||||||
viewModel.getCities(provinceId)
|
binding.autoCompleteProvinsi.showDropDown()
|
||||||
binding.autoCompleteKabupaten.text.clear()
|
}
|
||||||
|
|
||||||
|
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, _ ->
|
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
|
||||||
cityAdapter.getCityId(position)?.let { cityId ->
|
val cityId = cityAdapter.getCityId(position)
|
||||||
viewModel.selectedCityId = cityId
|
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() {
|
private fun setupObservers() {
|
||||||
lifecycleScope.launch {
|
Log.d(TAG, "Setting up LiveData observers")
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
launch {
|
|
||||||
viewModel.provincesState.collect { state ->
|
|
||||||
handleProvinceState(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
launch {
|
// Observe provinces
|
||||||
viewModel.citiesState.collect { state ->
|
viewModel.provincesState.observe(this) { state ->
|
||||||
handleCityState(state)
|
Log.d(TAG, "Received provincesState update: $state")
|
||||||
}
|
handleProvinceState(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
// Observe cities
|
||||||
viewModel.addressSubmissionState.collect { state ->
|
viewModel.citiesState.observe(this) { state ->
|
||||||
handleAddressSubmissionState(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<List<ProvincesItem>>) {
|
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ViewState.Loading -> null //showProvinceLoading(true)
|
is ViewState.Loading -> {
|
||||||
|
Log.d("AddAddressActivity", "Loading provinces...")
|
||||||
|
// Show loading indicator
|
||||||
|
}
|
||||||
is ViewState.Success -> {
|
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 -> {
|
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<List<CitiesItem>>) {
|
private fun handleCityState(state: ViewState<List<CitiesItem>>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ViewState.Loading -> null //showCityLoading(true)
|
is ViewState.Loading -> {
|
||||||
|
Log.d("AddAddressActivity", "Loading cities...")
|
||||||
|
binding.cityProgressBar.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
is ViewState.Success -> {
|
is ViewState.Success -> {
|
||||||
// showCityLoading(false)
|
Log.d("AddAddressActivity", "Cities loaded: ${state.data.size}")
|
||||||
|
binding.cityProgressBar.visibility = View.GONE
|
||||||
cityAdapter.updateData(state.data)
|
cityAdapter.updateData(state.data)
|
||||||
}
|
}
|
||||||
is ViewState.Error -> {
|
is ViewState.Error -> {
|
||||||
// showCityLoading(false)
|
binding.cityProgressBar.visibility = View.GONE
|
||||||
showError(state.message)
|
showError("Failed to load cities: ${state.message}")
|
||||||
|
Log.e("AddAddressActivity", "City error: ${state.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ViewState.Loading -> null //showSubmitLoading(true)
|
is ViewState.Loading -> {
|
||||||
|
Log.d(TAG, "Address submission: Loading")
|
||||||
|
showSubmitLoading(true)
|
||||||
|
}
|
||||||
is ViewState.Success -> {
|
is ViewState.Success -> {
|
||||||
|
Log.d(TAG, "Address submission: Success - ${state.data}")
|
||||||
showSubmitLoading(false)
|
showSubmitLoading(false)
|
||||||
showSuccessAndFinish(state.data)
|
showSuccessAndFinish(state.data)
|
||||||
}
|
}
|
||||||
is ViewState.Error -> {
|
is ViewState.Error -> {
|
||||||
|
Log.e(TAG, "Address submission: Error - ${state.message}")
|
||||||
showSubmitLoading(false)
|
showSubmitLoading(false)
|
||||||
showError(state.message)
|
showError(state.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSubmitLoading(isLoading: Boolean) {
|
private fun showSubmitLoading(isLoading: Boolean) {
|
||||||
binding.buttonSimpan.isEnabled = !isLoading
|
binding.buttonSimpan.isEnabled = !isLoading
|
||||||
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
|
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) {
|
private fun showError(message: String) {
|
||||||
@ -177,47 +238,83 @@ private fun setupToolbar() {
|
|||||||
|
|
||||||
private fun showSuccessAndFinish(message: String) {
|
private fun showSuccessAndFinish(message: String) {
|
||||||
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
||||||
onBackPressed()
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateAndSubmitForm() {
|
private fun validateAndSubmitForm() {
|
||||||
val lat = latitude
|
Log.d(TAG, "Validating form...")
|
||||||
val long = longitude
|
Log.d(TAG, "Current location: lat=$latitude, long=$longitude")
|
||||||
|
|
||||||
if (lat == null || long == null) {
|
// Check if we have location - always use default if not available
|
||||||
showError("Lokasi belum terdeteksi")
|
if (latitude == null || longitude == null) {
|
||||||
return
|
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 street = binding.etDetailAlamat.text.toString().trim()
|
||||||
val subDistrict = binding.etKecamatan.text.toString()
|
val subDistrict = binding.etKecamatan.text.toString().trim()
|
||||||
val postalCode = binding.etKodePos.text.toString()
|
val postalCode = binding.etKodePos.text.toString().trim()
|
||||||
val recipient = binding.etNamaPenerima.text.toString()
|
val recipient = binding.etNamaPenerima.text.toString().trim()
|
||||||
val phone = binding.etNomorHp.text.toString()
|
val phone = binding.etNomorHp.text.toString().trim()
|
||||||
val userId = profileUser.userId
|
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 isStoreLocation = false
|
||||||
|
|
||||||
val provinceId = viewModel.selectedProvinceId
|
val provinceId = viewModel.selectedProvinceId
|
||||||
val cityId = viewModel.selectedCityId
|
val cityId = viewModel.selectedCityId
|
||||||
|
|
||||||
if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
|
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " +
|
||||||
showError("Lengkapi semua field wajib")
|
"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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provinceId == null) {
|
if (provinceId == null) {
|
||||||
|
Log.w(TAG, "Validation failed: provinceId is null")
|
||||||
showError("Pilih provinsi terlebih dahulu")
|
showError("Pilih provinsi terlebih dahulu")
|
||||||
|
binding.autoCompleteProvinsi.requestFocus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cityId == null) {
|
if (cityId == null) {
|
||||||
|
Log.w(TAG, "Validation failed: cityId is null")
|
||||||
showError("Pilih kota/kabupaten terlebih dahulu")
|
showError("Pilih kota/kabupaten terlebih dahulu")
|
||||||
|
binding.autoCompleteKabupaten.requestFocus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create request with all fields
|
||||||
val request = CreateAddressRequest(
|
val request = CreateAddressRequest(
|
||||||
lat = lat,
|
lat = latitude!!, // Safe to use !! as we've checked above
|
||||||
long = long,
|
long = longitude!!,
|
||||||
street = street,
|
street = street,
|
||||||
subDistrict = subDistrict,
|
subDistrict = subDistrict,
|
||||||
cityId = cityId,
|
cityId = cityId,
|
||||||
@ -230,12 +327,17 @@ private fun setupToolbar() {
|
|||||||
isStoreLocation = isStoreLocation
|
isStoreLocation = isStoreLocation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Log.d(TAG, "Form validation successful, submitting address: $request")
|
||||||
viewModel.addAddress(request)
|
viewModel.addAddress(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val locationPermissionLauncher =
|
private val locationPermissionLauncher =
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
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() {
|
private fun requestLocationPermission() {
|
||||||
@ -244,36 +346,164 @@ private fun setupToolbar() {
|
|||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun requestLocation() {
|
private fun requestLocation() {
|
||||||
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
Log.d(TAG, "Requesting device location")
|
||||||
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
|
||||||
|
|
||||||
if (!isGpsEnabled && !isNetworkEnabled) {
|
// Check if we're already requesting location to avoid multiple requests
|
||||||
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
|
if (isRequestingLocation) {
|
||||||
|
Log.w(TAG, "Location request already in progress")
|
||||||
return
|
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) {
|
override fun onLocationChanged(location: Location) {
|
||||||
|
Log.d(TAG, "onLocationChanged called: lat=${location.latitude}, long=${location.longitude}")
|
||||||
latitude = location.latitude
|
latitude = location.latitude
|
||||||
longitude = location.longitude
|
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) {
|
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<out String>, grantResults: IntArray) {
|
try {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
// Request location updates
|
||||||
if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
Log.d(TAG, "Requesting location updates from $provider")
|
||||||
requestLocation()
|
locationManager.requestLocationUpdates(
|
||||||
} else {
|
provider,
|
||||||
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
|
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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.address
|
package com.alya.ecommerce_serang.ui.order.address
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
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.CitiesItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
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.OrderRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
class AddAddressViewModel(private val repository: OrderRepository, private val userRepo: UserRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
||||||
// Flow states for data
|
private val _addressSubmissionState = MutableLiveData<ViewState<String>>()
|
||||||
private val _addressSubmissionState = MutableStateFlow<ViewState<String>>(ViewState.Loading)
|
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
|
||||||
val addressSubmissionState = _addressSubmissionState.asStateFlow()
|
|
||||||
|
|
||||||
private val _provincesState = MutableStateFlow<ViewState<List<ProvincesItem>>>(ViewState.Loading)
|
private val _userProfile = MutableLiveData<UserProfile?>()
|
||||||
val provincesState = _provincesState.asStateFlow()
|
val userProfile: LiveData<UserProfile?> = _userProfile
|
||||||
|
|
||||||
private val _citiesState = MutableStateFlow<ViewState<List<CitiesItem>>>(ViewState.Loading)
|
private val _errorMessageUser = MutableLiveData<String>()
|
||||||
val citiesState = _citiesState.asStateFlow()
|
val errorMessageUser : LiveData<String> = _errorMessageUser
|
||||||
|
|
||||||
|
private val _provincesState = MutableLiveData<ViewState<List<ProvincesItem>>>()
|
||||||
|
val provincesState: LiveData<ViewState<List<ProvincesItem>>> = _provincesState
|
||||||
|
|
||||||
|
private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>()
|
||||||
|
val citiesState: LiveData<ViewState<List<CitiesItem>>> = _citiesState
|
||||||
|
|
||||||
// Stored in SavedStateHandle for configuration changes
|
// Stored in SavedStateHandle for configuration changes
|
||||||
var selectedProvinceId: Int?
|
var selectedProvinceId: Int?
|
||||||
@ -38,47 +45,82 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
|
|||||||
getProvinces()
|
getProvinces()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAddress(request: CreateAddressRequest){
|
fun addAddress(request: CreateAddressRequest) {
|
||||||
|
Log.d(TAG, "Starting address submission process")
|
||||||
|
_addressSubmissionState.value = ViewState.Loading
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (val result = repository.addAddress(request)) {
|
try {
|
||||||
is Result.Success -> {
|
Log.d(TAG, "Calling repository.addAddress with request: $request")
|
||||||
val message = result.data.message // Ambil `message` dari CreateAddressResponse
|
val result = repository.addAddress(request)
|
||||||
_addressSubmissionState.value = ViewState.Success(message)
|
|
||||||
}
|
when (result) {
|
||||||
is Result.Error -> {
|
is Result.Success -> {
|
||||||
_addressSubmissionState.value =
|
val message = result.data.message
|
||||||
ViewState.Error(result.exception.message ?: "Unknown error")
|
Log.d(TAG, "Address added successfully: $message")
|
||||||
}
|
_addressSubmissionState.postValue(ViewState.Success(message))
|
||||||
is Result.Loading -> {
|
}
|
||||||
// Optional, karena sudah set Loading di awal
|
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 {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = repository.getListProvinces()
|
val result = repository.getListProvinces()
|
||||||
result?.let {
|
if (result?.provinces != null) {
|
||||||
_provincesState.value = ViewState.Success(it.provinces)
|
_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) {
|
} 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){
|
fun getCities(provinceId: Int){
|
||||||
|
_citiesState.value = ViewState.Loading
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
selectedProvinceId = provinceId
|
selectedProvinceId = provinceId
|
||||||
val result = repository.getListCities(provinceId)
|
val result = repository.getListCities(provinceId)
|
||||||
result?.let {
|
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) {
|
} 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
|
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 {
|
companion object {
|
||||||
private const val TAG = "AddAddressViewModel"
|
private const val TAG = "AddAddressViewModel"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.address
|
package com.alya.ecommerce_serang.ui.order.address
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
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.api.response.order.ProvincesItem
|
||||||
@ -20,6 +21,8 @@ class ProvinceAdapter(
|
|||||||
clear()
|
clear()
|
||||||
addAll(provinces.map { it.province })
|
addAll(provinces.map { it.province })
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
|
|
||||||
|
Log.d("ProvinceAdapter", "Updated with ${provinces.size} provinces")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProvinceId(position: Int): Int? {
|
fun getProvinceId(position: Int): Int? {
|
||||||
|
@ -115,10 +115,21 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="none"
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
|
||||||
<!-- Kabupaten / Kota -->
|
<!-- Kabupaten / Kota -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -138,11 +149,20 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="none"
|
android:inputType="none"
|
||||||
android:hint="Masukkan Kabupaten"
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/cityProgressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- Kecamatan / Desa -->
|
<!-- Kecamatan / Desa -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -188,6 +208,8 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/buttonSimpan"
|
android:id="@+id/buttonSimpan"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -200,5 +222,60 @@
|
|||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/submitProgressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttonSimpan"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttonSimpan"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Status Lokasi"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLocationStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Menunggu lokasi..."
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/locationProgressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnReloadLocation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:text="Reload"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -287,8 +287,7 @@
|
|||||||
android:id="@+id/rv_payment_methods"
|
android:id="@+id/rv_payment_methods"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/item_payment_method"
|
tools:listitem="@layout/item_payment_method" />
|
||||||
tools:itemCount="2" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
app:title="Pengiriman" />
|
app:title="Pengiriman" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_shipment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@ -29,4 +30,13 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/item_shipping_order"/>
|
tools:listitem="@layout/item_shipping_order"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/linear_shipment"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Reference in New Issue
Block a user