fix bgt order (-paymentId)

This commit is contained in:
shaulascr
2025-04-13 19:17:43 +07:00
parent cc52888efd
commit 3051732b1e
15 changed files with 777 additions and 300 deletions

View File

@ -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 (

View File

@ -11,46 +11,25 @@ data class OrderDetailResponse(
val message: String val message: String
) )
data class OrderItemsItem(
@field:SerializedName("review_id")
val reviewId: Int? = null,
@field:SerializedName("quantity")
val quantity: Int,
@field:SerializedName("price")
val price: Int,
@field:SerializedName("subtotal")
val subtotal: Int,
@field:SerializedName("product_image")
val productImage: String? = null,
@field:SerializedName("store_name")
val storeName: String,
@field:SerializedName("product_price")
val productPrice: Int,
@field:SerializedName("product_name")
val productName: String
)
data class Orders( data class Orders(
@field:SerializedName("receipt_num") @field:SerializedName("receipt_num")
val receiptNum: String, val receiptNum: String,
@field:SerializedName("payment_upload_at")
val paymentUploadAt: String,
@field:SerializedName("latitude") @field:SerializedName("latitude")
val latitude: String, val latitude: String,
@field:SerializedName("pay_info_name")
val payInfoName: String,
@field:SerializedName("created_at") @field:SerializedName("created_at")
val createdAt: String, val createdAt: String,
@field:SerializedName("voucher_code") @field:SerializedName("voucher_code")
val voucherCode: String? = null, val voucherCode: Any,
@field:SerializedName("updated_at") @field:SerializedName("updated_at")
val updatedAt: String, val updatedAt: String,
@ -64,6 +43,9 @@ data class Orders(
@field:SerializedName("cancel_date") @field:SerializedName("cancel_date")
val cancelDate: String, val cancelDate: String,
@field:SerializedName("payment_evidence")
val paymentEvidence: String,
@field:SerializedName("longitude") @field:SerializedName("longitude")
val longitude: String, val longitude: String,
@ -79,14 +61,20 @@ data class Orders(
@field:SerializedName("is_store_location") @field:SerializedName("is_store_location")
val isStoreLocation: Boolean, val isStoreLocation: Boolean,
@field:SerializedName("qris_image")
val qrisImage: String,
@field:SerializedName("voucher_name") @field:SerializedName("voucher_name")
val voucherName: String? = null, val voucherName: Any,
@field:SerializedName("payment_status")
val paymentStatus: String,
@field:SerializedName("address_id") @field:SerializedName("address_id")
val addressId: Int, val addressId: Int,
@field:SerializedName("payment_method_id") @field:SerializedName("payment_amount")
val paymentMethodId: Int, val paymentAmount: String,
@field:SerializedName("cancel_reason") @field:SerializedName("cancel_reason")
val cancelReason: String, val cancelReason: String,
@ -109,11 +97,17 @@ data class Orders(
@field:SerializedName("service") @field:SerializedName("service")
val service: String, val service: String,
@field:SerializedName("pay_info_num")
val payInfoNum: String,
@field:SerializedName("shipment_price") @field:SerializedName("shipment_price")
val shipmentPrice: String, val shipmentPrice: String,
@field:SerializedName("voucher_id") @field:SerializedName("voucher_id")
val voucherId: Int? = null, val voucherId: Any,
@field:SerializedName("payment_info_id")
val paymentInfoId: Int,
@field:SerializedName("detail") @field:SerializedName("detail")
val detail: String, val detail: String,
@ -127,3 +121,36 @@ data class Orders(
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int val cityId: Int
) )
data class OrderItemsItem(
@field:SerializedName("order_item_id")
val orderItemId: Int,
@field:SerializedName("review_id")
val reviewId: Int,
@field:SerializedName("quantity")
val quantity: Int,
@field:SerializedName("price")
val price: Int,
@field:SerializedName("subtotal")
val subtotal: Int,
@field:SerializedName("product_image")
val productImage: String,
@field:SerializedName("product_id")
val productId: Int,
@field:SerializedName("store_name")
val storeName: String,
@field:SerializedName("product_price")
val productPrice: Int,
@field:SerializedName("product_name")
val productName: String
)

View File

@ -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,

View File

@ -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 { } else {
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}") Log.e("OrderRepository", "Response body was null")
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error")) Result.Error(Exception("Empty response from server"))
}
} else {
val errorBody = 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)
}
}
} }

View File

@ -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 {

View File

@ -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 { } else {
// For Cart checkout // For Cart checkout
val cartRequest = currentData.orderRequest as OrderRequest val cartRequest = currentData.orderRequest as OrderRequest
val updatedRequest = cartRequest.copy(paymentMethodId = paymentId) val updatedRequest = cartRequest.copy(paymentMethodId = currentPaymentId)
_checkoutData.value = currentData.copy(orderRequest = updatedRequest) _checkoutData.value = currentData.copy(orderRequest = updatedRequest)
} }
} else {
// If no matching payment method is found
_errorMessage.value = "Selected payment method not found"
}
} 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)
} }
} }
} }

View File

@ -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)
}
} }
} }

View File

@ -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"

View File

@ -36,12 +36,14 @@ class ShippingViewModel(
_errorMessage.value = "" _errorMessage.value = ""
// Prepare the request // Prepare the request
val request = CourierCostRequest( val costProduct = CostProduct(
addressId = addressId,
itemCost = CostProduct(
productId = productId, productId = productId,
quantity = quantity quantity = quantity
) )
val request = CourierCostRequest(
addressId = addressId,
itemCost = listOf(costProduct) // Wrap in a list
) )
viewModelScope.launch { viewModelScope.launch {

View File

@ -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
setupToolbar() // Get user profile from session manager
setupAutoComplete() // profileUser =UserProfile.
setupButtonListeners() viewModel.userProfile.observe(this){ user ->
collectFlows() user?.let { updateProfile(it) }
requestLocationPermission() }
setupToolbar()
requestLocationPermission()
setupReloadButtons()
setupAutoComplete()
setupButtonListeners()
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 // 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()
} }
} }
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ -> // Set listeners for selection
cityAdapter.getCityId(position)?.let { cityId -> binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
viewModel.selectedCityId = cityId 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, _ ->
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() { private fun setupObservers() {
lifecycleScope.launch { Log.d(TAG, "Setting up LiveData observers")
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { // Observe provinces
viewModel.provincesState.collect { state -> viewModel.provincesState.observe(this) { state ->
Log.d(TAG, "Received provincesState update: $state")
handleProvinceState(state) handleProvinceState(state)
} }
}
launch { // Observe cities
viewModel.citiesState.collect { state -> viewModel.citiesState.observe(this) { state ->
Log.d(TAG, "Received citiesState update: $state")
handleCityState(state) handleCityState(state)
} }
}
launch { // Observe address submission
viewModel.addressSubmissionState.collect { state -> viewModel.addressSubmissionState.observe(this) { state ->
Log.d(TAG, "Received addressSubmissionState update: $state")
handleAddressSubmissionState(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 -> {
Log.d("AddAddressActivity", "Provinces loaded: ${state.data.size}")
// Hide loading indicator
if (state.data.isNotEmpty()) {
provinceAdapter.updateData(state.data) 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"
}
} }

View File

@ -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 {
Log.d(TAG, "Calling repository.addAddress with request: $request")
val result = repository.addAddress(request)
when (result) {
is Result.Success -> { is Result.Success -> {
val message = result.data.message // Ambil `message` dari CreateAddressResponse val message = result.data.message
_addressSubmissionState.value = ViewState.Success(message) Log.d(TAG, "Address added successfully: $message")
_addressSubmissionState.postValue(ViewState.Success(message))
} }
is Result.Error -> { is Result.Error -> {
_addressSubmissionState.value = val errorMsg = result.exception.message ?: "Unknown error"
ViewState.Error(result.exception.message ?: "Unknown error") Log.e(TAG, "Error from repository: $errorMsg", result.exception)
_addressSubmissionState.postValue(ViewState.Error(errorMsg))
} }
is Result.Loading -> { is Result.Loading -> {
// Optional, karena sudah set Loading di awal 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"
} }

View File

@ -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? {

View File

@ -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>

View File

@ -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

View File

@ -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>