mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
fix bgt order (-paymentId)
This commit is contained in:
@ -7,7 +7,7 @@ data class CourierCostRequest(
|
||||
val addressId: Int,
|
||||
|
||||
@SerializedName("items")
|
||||
val itemCost: CostProduct
|
||||
val itemCost: List<CostProduct>
|
||||
)
|
||||
|
||||
data class CostProduct (
|
||||
|
@ -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<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(
|
||||
|
||||
@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<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(
|
||||
|
||||
val id: Int = 1,
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
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.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<CreateAddressResponse> {
|
||||
suspend fun addAddress(request: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||
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<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>) {
|
||||
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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ class PaymentMethodAdapter(
|
||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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<List<ProvincesItem>>) {
|
||||
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<List<CitiesItem>>) {
|
||||
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<String>) {
|
||||
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<out String>, 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"
|
||||
}
|
||||
}
|
||||
|
@ -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<String>>(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<ViewState<String>>()
|
||||
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
|
||||
|
||||
private val _provincesState = MutableStateFlow<ViewState<List<ProvincesItem>>>(ViewState.Loading)
|
||||
val provincesState = _provincesState.asStateFlow()
|
||||
private val _userProfile = MutableLiveData<UserProfile?>()
|
||||
val userProfile: LiveData<UserProfile?> = _userProfile
|
||||
|
||||
private val _citiesState = MutableStateFlow<ViewState<List<CitiesItem>>>(ViewState.Loading)
|
||||
val citiesState = _citiesState.asStateFlow()
|
||||
private val _errorMessageUser = MutableLiveData<String>()
|
||||
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
|
||||
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"
|
||||
}
|
||||
|
@ -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? {
|
||||
|
@ -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" />
|
||||
</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 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -138,11 +149,20 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:hint="Masukkan Kabupaten"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
</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 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -188,6 +208,8 @@
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonSimpan"
|
||||
android:layout_width="match_parent"
|
||||
@ -200,5 +222,60 @@
|
||||
android:textSize="16sp"
|
||||
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>
|
@ -287,8 +287,7 @@
|
||||
android:id="@+id/rv_payment_methods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_payment_method"
|
||||
tools:itemCount="2" />
|
||||
tools:listitem="@layout/item_payment_method" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
@ -18,6 +18,7 @@
|
||||
app:title="Pengiriman" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_shipment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@ -29,4 +30,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_shipping_order"/>
|
||||
</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>
|
Reference in New Issue
Block a user