diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/ProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/ProductResponse.kt index efc26fc..e6ad539 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/ProductResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/ProductResponse.kt @@ -82,7 +82,8 @@ data class Product( data class CartItemCheckoutInfo( val cartItem: CartItemsItem, - val isWholesale: Boolean + val isWholesale: Boolean, + val wholesalePrice: Int? = null ) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/profile/AddressDetailResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/profile/AddressDetailResponse.kt index e2ed873..b051263 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/profile/AddressDetailResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/profile/AddressDetailResponse.kt @@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName data class AddressDetailResponse( @field:SerializedName("address") - val address: Address, + val address: AddressDetail, @field:SerializedName("message") val message: String @@ -14,7 +14,7 @@ data class AddressDetailResponse( data class AddressDetail( @field:SerializedName("village_id") - val villageId: String, + val villageId: String?, @field:SerializedName("is_store_location") val isStoreLocation: Boolean, @@ -38,7 +38,7 @@ data class AddressDetail( val provinceId: String, @field:SerializedName("phone") - val phone: String, + val phone: String?, @field:SerializedName("street") val street: String, @@ -47,7 +47,7 @@ data class AddressDetail( val subdistrict: String, @field:SerializedName("recipient") - val recipient: String, + val recipient: String?, @field:SerializedName("id") val id: Int, diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt index f17c2f9..59250bb 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt @@ -63,6 +63,7 @@ import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreP import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetailResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse @@ -528,4 +529,9 @@ interface ApiService { suspend fun postResetPass( @Body request: ResetPassReq ): Response + + @GET("address/detail/{id}") + suspend fun getDetailAddress( + @Path("id") addressId: Int + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt index f9eda36..688df18 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt @@ -20,12 +20,16 @@ import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityRespon import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse +import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictResponse +import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesResponse import com.alya.ecommerce_serang.data.api.response.customer.product.DetailPaymentItem import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetailResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse +import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse @@ -294,6 +298,30 @@ class OrderRepository(private val apiService: ApiService) { return if (response.isSuccessful) response.body() else null } + suspend fun getListSubdistrict(cityId : String): SubdistrictResponse? { + val response = apiService.getSubdistrict(cityId) + return if (response.isSuccessful) response.body() else null + } + + suspend fun getListVillages(subId: String): VillagesResponse? { + val response = apiService.getVillages(subId) + return if (response.isSuccessful) response.body() else null + } + + suspend fun getAddressDetail(addressId: Int): AddressDetailResponse? { + return try { + val response = apiService.getDetailAddress(addressId) + if (response.isSuccessful) response.body() else null + } catch (e: Exception) { + Log.e("OrderRepository", "Error getting address detail $e ") + null + } + } + + suspend fun updateAddress(addressId: Int, params: Map): Response { + return apiService.updateAddress(addressId, params) + } + suspend fun fetchUserProfile(): Result { return try { val response = apiService.getUserProfile() @@ -319,6 +347,7 @@ class OrderRepository(private val apiService: ApiService) { } } + suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result { return try { Log.d("OrderRepository", "Uploading payment proof...") diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt index 6a6c69d..b5fba26 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt @@ -30,6 +30,8 @@ class CartActivity : AppCompatActivity() { private lateinit var sessionManager: SessionManager private lateinit var storeAdapter: StoreAdapter + private var TAG = "Cart Activity" + private val viewModel: CartViewModel by viewModels { BaseViewModelFactory { val apiService = ApiConfig.getApiService(sessionManager) @@ -135,11 +137,33 @@ class CartActivity : AppCompatActivity() { } private fun startCheckoutWithWholesaleInfo(checkoutItems: List) { - // Extract cart item IDs and wholesale status - val cartItemIds = checkoutItems.map { it.cartItem.cartItemId } - val wholesaleArray = checkoutItems.map { it.isWholesale }.toBooleanArray() + val wholesalePriceMap = viewModel.cartItemWholesalePrice.value ?: emptyMap() + + val updatedItems = checkoutItems.map { info -> + val wholesalePrice = wholesalePriceMap[info.cartItem.cartItemId] + + val updatedCartItem = if (info.isWholesale && wholesalePrice != null) { + // Replace the price with wholesale price + info.cartItem.copy(price = wholesalePrice.toInt()) + } else { + info.cartItem + } + + // Debug log + Log.d( + TAG, + "cartItemId: ${updatedCartItem.cartItemId}, " + + "isWholesale: ${info.isWholesale}, " + + "wholesalePrice: $wholesalePrice, " + + "finalPrice: ${updatedCartItem.price}" + ) + + info.copy(cartItem = updatedCartItem) + } + + val cartItemIds = updatedItems.map { it.cartItem.cartItemId } + val wholesaleArray = updatedItems.map { it.isWholesale }.toBooleanArray() - // Start checkout activity with the cart items and wholesale info CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray) } @@ -233,7 +257,5 @@ class CartActivity : AppCompatActivity() { val format = NumberFormat.getCurrencyInstance(Locale("id", "ID")) return format.format(amount).replace("Rp", "Rp ") } - - } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt index 0a0676e..dedb63a 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt @@ -10,6 +10,9 @@ 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.customer.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem +import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem +import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetail import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.UserRepository @@ -31,6 +34,18 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u private val _citiesState = MutableLiveData>>() val citiesState: LiveData>> = _citiesState + private val _subdistrictState = MutableLiveData>>() + val subdistrictState: LiveData>> = _subdistrictState + + private val _villagesState = MutableLiveData>>() + val villagesState: LiveData>> = _villagesState + + private val _userAddress = MutableLiveData() + val userAddress: LiveData = _userAddress + + private val _editAddress = MutableLiveData() + val editAddress: LiveData get() = _editAddress + // Stored in SavedStateHandle for configuration changes var selectedProvinceId: Int? get() = savedStateHandle.get("selectedProvinceId") @@ -40,6 +55,9 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u get() = savedStateHandle.get("selectedCityId") set(value) { savedStateHandle["selectedCityId"] = value } + var selectedSubdistrict: String? = null + var selectedVillages: String? = null + init { // Load provinces on initialization getProvinces() @@ -86,6 +104,8 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u } } + + fun getProvinces() { _provincesState.value = ViewState.Loading viewModelScope.launch { @@ -125,24 +145,113 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u } } + fun getSubdistrict(cityId: String) { + _subdistrictState.value = Result.Loading + viewModelScope.launch { + try { + + selectedSubdistrict = cityId + val result = repository.getListSubdistrict(cityId) + result?.let { + _subdistrictState.postValue(Result.Success(it.subdistricts)) + Log.d(TAG, "Cities loaded for province $cityId: ${it.subdistricts.size}") + } ?: run { + _subdistrictState.postValue(Result.Error(Exception("Failed to load cities"))) + Log.e(TAG, "City result was null for province $cityId") + } + } catch (e: Exception) { + _subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities"))) + Log.e(TAG, "Error fetching cities for province $cityId", e) + } + } + } + + fun getVillages(subdistrictId: String) { + _villagesState.value = Result.Loading + viewModelScope.launch { + try { + + selectedVillages = subdistrictId + val result = repository.getListVillages(subdistrictId) + result?.let { + _villagesState.postValue(Result.Success(it.villages)) + Log.d(TAG, "Cities loaded for province $subdistrictId: ${it.villages.size}") + } ?: run { + _villagesState.postValue(Result.Error(Exception("Failed to load cities"))) + Log.e(TAG, "City result was null for province $subdistrictId") + } + } catch (e: Exception) { + _villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities"))) + Log.e(TAG, "Error fetching cities for province $subdistrictId", e) + } + } + } + fun setSelectedProvinceId(id: Int) { selectedProvinceId = id } - fun updateSelectedCityId(id: String) { - selectedCityId = id - } - - fun loadUserProfile(){ + fun detailAddress(addressId: Int){ 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 + try { + val response = repository.getAddressDetail(addressId) + if (response != null){ + _userAddress.value = response.address + } else { + Log.e(TAG, "Failed load address detail") + } + + } catch (e:Exception){ + Log.e(TAG, "Error fetching address detail: $e", e) } } } + fun updateAddress(oldAddress: AddressDetail, newAddress: AddressDetail) { + val params = buildUpdateBody(oldAddress, newAddress) + if (params.isEmpty()) { + Log.d(TAG, "No changes detected") + _editAddress.value = false + return + } + + viewModelScope.launch { + try { + val response = repository.updateAddress(oldAddress.id, params) + _editAddress.value = response.isSuccessful + } catch (e: Exception) { + Log.e(TAG, "Error: ${e.message}") + _editAddress.value = false + } + } + } + + private fun buildUpdateBody(oldAddress: AddressDetail, newAddress: AddressDetail): Map { + + val params = mutableMapOf() + + fun addIfChanged(key: String, oldValue: Any?, newValue: Any?) { + if (newValue != null && newValue != oldValue) { + params[key] = newValue + } + } + + addIfChanged("street", oldAddress.street, newAddress.street) + addIfChanged("province_id", oldAddress.provinceId, newAddress.provinceId) + addIfChanged("detail", oldAddress.detail, newAddress.detail) + addIfChanged("subdistrict", oldAddress.subdistrict, newAddress.subdistrict) + addIfChanged("city_id", oldAddress.cityId, newAddress.cityId) + addIfChanged("village_id", oldAddress.villageId, newAddress.villageId) + addIfChanged("postal_code", oldAddress.postalCode, newAddress.postalCode) + addIfChanged("phone", oldAddress.phone, newAddress.phone) + addIfChanged("recipient", oldAddress.recipient, newAddress.recipient) + addIfChanged("latitude", oldAddress.latitude, newAddress.latitude) + addIfChanged("longitude", oldAddress.longitude, newAddress.longitude) + addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation) + + return params + } + companion object { private const val TAG = "AddAddressViewModel" } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt index 40dc280..1237eaa 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt @@ -74,13 +74,17 @@ class AddressActivity : AppCompatActivity() { } private fun setupRecyclerView() { - adapter = AddressAdapter { address -> - // Select the address in the ViewModel - viewModel.selectAddress(address.id) - - // Return immediately with the selected address - returnResultAndFinish(address.id) - } + adapter = AddressAdapter( + onAddressClick = { address -> + viewModel.selectAddress(address.id) + returnResultAndFinish(address.id) + }, + onEditClick = { address -> + val intent = Intent(this, EditAddressActivity::class.java) + intent.putExtra(EditAddressActivity.EXTRA_ADDRESS_ID, address.id) + startActivity(intent) + } + ) binding.rvSellerOrder.apply { layoutManager = LinearLayoutManager(this@AddressActivity) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt index 84c6476..db9933d 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt @@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order.address import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil @@ -13,7 +14,8 @@ import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesIte import com.google.android.material.card.MaterialCardView class AddressAdapter( - private val onAddressClick: (AddressesItem) -> Unit + private val onAddressClick: (AddressesItem) -> Unit, + private val onEditClick: (AddressesItem) -> Unit ) : ListAdapter(DIFF_CALLBACK) { private var selectedAddressId: Int? = null @@ -47,18 +49,21 @@ class AddressAdapter( // Pass the whole address object to provide more context onAddressClick(address) } + holder.editButton.setOnClickListener { + onEditClick(address) + } } class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val tvName: TextView = itemView.findViewById(R.id.tv_name_address) private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address) + val editButton: ImageView = itemView.findViewById(R.id.iv_edit) private val card: MaterialCardView = itemView as MaterialCardView fun bind(address: AddressesItem, isSelected: Boolean) { tvName.text = address.recipient tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}" - // Make selection more visible card.strokeWidth = if (isSelected) 3 else 0 card.strokeColor = if (isSelected) ContextCompat.getColor(itemView.context, R.color.blue_400) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt index 574a7a8..5724703 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt @@ -1,21 +1,418 @@ package com.alya.ecommerce_serang.ui.order.address import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Toast import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem +import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem +import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem +import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetail +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.Result +import com.alya.ecommerce_serang.data.repository.UserRepository +import com.alya.ecommerce_serang.databinding.ActivityEditAddressBinding +import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager class EditAddressActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContentView(R.layout.activity_edit_address) - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets + + private lateinit var binding: ActivityEditAddressBinding + private lateinit var apiService: ApiService + private lateinit var sessionManager: SessionManager + + private var latitude: Double? = null + private var longitude: Double? = null + private var addressId: Int = -1 + private var currentAddress: AddressDetail? = null + private val provinceAdapter by lazy { ProvinceAdapter(this) } + private val cityAdapter by lazy { CityAdapter(this) } + private val subdistrictAdapter by lazy { SubdsitrictAdapter(this)} + private val villageAdapter by lazy { VillagesAdapter(this)} + + private var provincesList = mutableListOf() + private var citiesList = mutableListOf() + private var subdistrictsList = mutableListOf() + private var villagesList = mutableListOf() + + private val viewModel: AddAddressViewModel by viewModels { + SavedStateViewModelFactory(this) { savedStateHandle -> + val apiService = ApiConfig.getApiService(sessionManager) + val orderRepository = OrderRepository(apiService) + val userRepository = UserRepository(apiService) + AddAddressViewModel(orderRepository, userRepository, savedStateHandle) } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityEditAddressBinding.inflate(layoutInflater) + setContentView(binding.root) + + sessionManager = SessionManager(this) + apiService = ApiConfig.getApiService(sessionManager) + WindowCompat.setDecorFitsSystemWindows(window, false) + + enableEdgeToEdge() + + // Apply insets to your root layout + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.setPadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom + ) + windowInsets + } + + addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, -1) + if (addressId == -1) { + Toast.makeText(this, "Gagal mendapatkan alamat pengguna", Toast.LENGTH_SHORT).show() + finish() + return + } + + setupToolbar() + setupAdapters() + setupObservers() + setupListeners() + + // Load address detail first + Log.d(TAG, "Loading address with ID: $addressId") + viewModel.detailAddress(addressId) + } + + private fun setupToolbar() { + binding.toolbar.setNavigationOnClickListener { + onBackPressedDispatcher.onBackPressed() + } + } + + private fun setupAdapters() { + // Set custom adapters to AutoCompleteTextViews + binding.autoCompleteProvinsi.setAdapter(provinceAdapter) + binding.autoCompleteKabupaten.setAdapter(cityAdapter) + binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter) + binding.autoCompleteDesa.setAdapter(villageAdapter) + } + + private fun setupObservers() { + // Observe address detail + viewModel.userAddress.observe(this) { address -> + currentAddress = address + Log.d(TAG, "Address loaded: $address") + populateAddressData(address) + } + + // Observe provinces + viewModel.provincesState.observe(this) { viewState -> + when (viewState) { + is ViewState.Loading -> { + binding.progressBar.visibility = View.VISIBLE + } + is ViewState.Success -> { + binding.progressBar.visibility = View.GONE + provincesList.clear() + provincesList.addAll(viewState.data) + provinceAdapter.updateData(viewState.data) + + // Set selected province if address is loaded + currentAddress?.let { address -> + setSelectedProvince(address.provinceId.toInt()) + } + } + is ViewState.Error -> { + binding.progressBar.visibility = View.GONE + Log.e(TAG, "Failed to load province ${viewState.message}") + } + } + } + + // Observe cities + viewModel.citiesState.observe(this) { viewState -> + when (viewState) { + is ViewState.Loading -> { + binding.cityProgressBar.visibility = View.VISIBLE + } + is ViewState.Success -> { + binding.cityProgressBar.visibility = View.GONE + citiesList.clear() + citiesList.addAll(viewState.data) + cityAdapter.updateData(viewState.data) + + // Set selected city if address is loaded + currentAddress?.let { address -> + setSelectedCity(address.cityId) + } + } + is ViewState.Error -> { + binding.cityProgressBar.visibility = View.GONE + Log.e(TAG, "Failed to load cities ${viewState.message}") + } + } + } + + // Observe subdistricts + viewModel.subdistrictState.observe(this) { result -> + when (result) { + is com.alya.ecommerce_serang.data.repository.Result.Loading -> { + // You can add loading indicator for subdistrict if needed + } + is com.alya.ecommerce_serang.data.repository.Result.Success -> { + subdistrictsList.clear() + subdistrictsList.addAll(result.data) + subdistrictAdapter.updateData(result.data) + + // Set selected subdistrict if address is loaded + currentAddress?.let { address -> + setSelectedSubdistrict(address.subdistrict) + } + } + is com.alya.ecommerce_serang.data.repository.Result.Error -> { + Log.e(TAG, "Failed to load subdistricy") + } + } + } + + // Observe villages + viewModel.villagesState.observe(this) { result -> + when (result) { + is com.alya.ecommerce_serang.data.repository.Result.Loading -> { + // You can add loading indicator for village if needed + } + is com.alya.ecommerce_serang.data.repository.Result.Success -> { + villagesList.clear() + villagesList.addAll(result.data) + villageAdapter.updateData(result.data) + + // Set selected village if address is loaded + currentAddress?.let { address -> + setSelectedVillage(address.villageId) + } + } + is Result.Error -> { + Log.e(TAG, "Failed to load villages") + } + } + } + + // Observe update result + viewModel.editAddress.observe(this) { isSuccess -> + binding.submitProgressBar.visibility = View.GONE + binding.buttonSimpan.isEnabled = true + + if (isSuccess) { + Log.d(TAG, "Address updated successfully") + finish() + } else { + Log.d(TAG, "Failed to update address") + } + } + } + + private fun setupListeners() { + // Province selection + binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ -> + val provinceId = provinceAdapter.getProvinceId(position) + provinceId?.let { id -> + viewModel.getCities(id) + + // Clear dependent dropdowns + clearCitySelection() + clearSubdistrictSelection() + clearVillageSelection() + } + } + + // City selection + binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ -> + val cityId = cityAdapter.getCityId(position) + cityId?.let { id -> + viewModel.getSubdistrict(id) + + // Clear dependent dropdowns + clearSubdistrictSelection() + clearVillageSelection() + } + } + + // Subdistrict selection + binding.autoCompleteKecamatan.setOnItemClickListener { _, _, position, _ -> + val subdistrictId = subdistrictAdapter.getSubdistrictId(position) + subdistrictId?.let { id -> + viewModel.getVillages(id) + + // Clear dependent dropdowns + clearVillageSelection() + } + } + + // Village selection - auto-populate postal code + binding.autoCompleteDesa.setOnItemClickListener { _, _, position, _ -> +// val postalCode = villageAdapter.getPostalCode(position) +// postalCode?.let { +// binding.etKodePos.setText(it) +// } + } + + // Save button + binding.buttonSimpan.setOnClickListener { + saveAddress() + } + } + + private fun populateAddressData(address: AddressDetail) { + binding.etNamaPenerima.setText(address.recipient ?: "") + binding.etNomorHp.setText(address.phone ?: "") + binding.etDetailAlamat.setText(address.detail ?: "") + binding.etKodePos.setText(address.postalCode ?: "") + + // Province will be set when provinces are loaded + // City, subdistrict, village will be set in their respective observers + } + + private fun setSelectedProvince(provinceId: Int?) { + provinceId?.let { id -> + val position = provincesList.indexOfFirst { it.provinceId?.toIntOrNull() == id } + if (position >= 0) { + val provinceName = provincesList[position].province + binding.autoCompleteProvinsi.setText(provinceName, false) + viewModel.getCities(id) + } + } + } + + private fun setSelectedCity(cityId: String?) { + cityId?.let { id -> + val position = citiesList.indexOfFirst { it.cityId?.toString() == id } + if (position >= 0) { + val cityName = citiesList[position].cityName + binding.autoCompleteKabupaten.setText(cityName, false) + viewModel.getSubdistrict(id) + } + } + } + + private fun setSelectedSubdistrict(subdistrictId: String?) { + subdistrictId?.let { id -> + val position = subdistrictsList.indexOfFirst { it.subdistrictId?.toString() == id } + if (position >= 0) { + val subdistrictName = subdistrictsList[position].subdistrictName + binding.autoCompleteKecamatan.setText(subdistrictName, false) + viewModel.getVillages(id) + } + } + } + + private fun setSelectedVillage(villageId: String?) { + villageId?.let { id -> + val position = villagesList.indexOfFirst { it.villageId?.toString() == id } + if (position >= 0) { + val villageName = villagesList[position].villageName + binding.autoCompleteDesa.setText(villageName, false) + } + } + } +// +// private fun populatePostalCodeFromVillage(villageId: String?) { +// villageId?.let { id -> +// val village = villagesList.find { it.villageId?.toString() == id } +// village?.postalCode?.let { postalCode -> +// if (binding.etKodePos.text.isNullOrEmpty()) { +// binding.etKodePos.setText(postalCode) +// } +// } +// } +// } + + private fun clearCitySelection() { + binding.autoCompleteKabupaten.setText("", false) + citiesList.clear() + cityAdapter.updateData(emptyList()) + } + + private fun clearSubdistrictSelection() { + binding.autoCompleteKecamatan.setText("", false) + subdistrictsList.clear() + subdistrictAdapter.updateData(emptyList()) + } + + private fun clearVillageSelection() { + binding.autoCompleteDesa.setText("", false) + binding.etKodePos.setText("") // Clear postal code when village is cleared + villagesList.clear() + villageAdapter.updateData(emptyList()) + } + + private fun saveAddress() { + currentAddress?.let { oldAddress -> + val newAddress = createNewAddressFromInputs(oldAddress) + + binding.submitProgressBar.visibility = View.VISIBLE + binding.buttonSimpan.isEnabled = false + + viewModel.updateAddress(oldAddress, newAddress) + } ?: run { + Log.d(TAG, "Address not loaded") + Toast.makeText(this, "Gagal mendapatkan alamat", Toast.LENGTH_SHORT).show() + } + } + + private fun createNewAddressFromInputs(oldAddress: AddressDetail): AddressDetail { + val selectedProvinceId = getSelectedProvinceId() + val selectedCityId = getSelectedCityId() + val selectedSubdistrictId = getSelectedSubdistrictId() + val selectedVillageId = getSelectedVillageId() + + return oldAddress.copy( + recipient = binding.etNamaPenerima.text.toString().trim(), + phone = binding.etNomorHp.text.toString().trim(), + detail = binding.etDetailAlamat.text.toString().trim(), + postalCode = binding.etKodePos.text.toString().trim(), + provinceId = selectedProvinceId.toString(), + cityId = selectedCityId.toString(), + subdistrict = selectedSubdistrictId.toString(), + villageId = selectedVillageId + ) + } + + private fun getSelectedProvinceId(): Int? { + val selectedText = binding.autoCompleteProvinsi.text.toString() + val position = provincesList.indexOfFirst { it.province == selectedText } + return if (position >= 0) provinceAdapter.getProvinceId(position) else null + } + + private fun getSelectedCityId(): String? { + val selectedText = binding.autoCompleteKabupaten.text.toString() + val position = citiesList.indexOfFirst { it.cityName == selectedText } + return if (position >= 0) cityAdapter.getCityId(position) else null + } + + private fun getSelectedSubdistrictId(): String? { + val selectedText = binding.autoCompleteKecamatan.text.toString() + val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText } + return if (position >= 0) subdistrictAdapter.getSubdistrictId(position) else null + } + + private fun getSelectedVillageId(): String? { + val selectedText = binding.autoCompleteDesa.text.toString() + val position = villagesList.indexOfFirst { it.villageName == selectedText } + return if (position >= 0) villageAdapter.getVillageId(position) else null + } + + companion object { + const val EXTRA_ADDRESS_ID = "extra_address_id" + private const val TAG = "EditAddressActivity" + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit_address.xml b/app/src/main/res/layout/activity_edit_address.xml index d0cea91..451776a 100644 --- a/app/src/main/res/layout/activity_edit_address.xml +++ b/app/src/main/res/layout/activity_edit_address.xml @@ -5,6 +5,257 @@ android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.order.address.EditAddressActivity"> + android:background="@android:color/white" + tools:context=".ui.order.address.AddAddressActivity"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +