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,
@SerializedName("items")
val itemCost: CostProduct
val itemCost: List<CostProduct>
)
data class CostProduct (

View File

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

View File

@ -13,6 +13,8 @@ data class DetailStoreProductResponse(
data class PaymentInfoItem(
val id: Int = 1,
@field:SerializedName("qris_image")
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.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)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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