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 b051263..885200d 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 @@ -23,13 +23,13 @@ data class AddressDetail( val latitude: String, @field:SerializedName("province_name") - val provinceName: String, + val provinceName: String?, @field:SerializedName("subdistrict_id") - val subdistrictId: String, + val subdistrictId: String?, @field:SerializedName("city_name") - val cityName: String, + val cityName: String?, @field:SerializedName("user_id") val userId: 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 59250bb..cd4f41e 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 @@ -530,7 +530,7 @@ interface ApiService { @Body request: ResetPassReq ): Response - @GET("address/detail/{id}") + @GET("profile/address/detail/{id}") suspend fun getDetailAddress( @Path("id") addressId: Int ): Response diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt index 8d524f7..4b29f78 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt @@ -24,9 +24,12 @@ import androidx.core.view.WindowInsetsCompat import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest 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.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.ActivityAddAddressBinding import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory @@ -45,6 +48,8 @@ class AddAddressActivity : AppCompatActivity() { private var longitude: Double? = 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 val viewModel: AddAddressViewModel by viewModels { SavedStateViewModelFactory(this) { savedStateHandle -> @@ -113,6 +118,8 @@ class AddAddressActivity : AppCompatActivity() { // Set adapters binding.autoCompleteProvinsi.setAdapter(provinceAdapter) binding.autoCompleteKabupaten.setAdapter(cityAdapter) + binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter) + binding.autoCompleteDesa.setAdapter(villageAdapter) // Make dropdown appear on click (not just when typing) binding.autoCompleteProvinsi.setOnClickListener { @@ -131,6 +138,26 @@ class AddAddressActivity : AppCompatActivity() { } } + binding.autoCompleteKecamatan.setOnClickListener{ + if (subdistrictAdapter.count > 0){ + Log.d(TAG, "Subdistrict clicked, dropdown with ${subdistrictAdapter.count} items") + binding.autoCompleteKecamatan.showDropDown() + } else { + Log.d(TAG, "No kecamatan available") + Toast.makeText(this, "Pilih Kabupaten / Kota terlebih dahulu", Toast.LENGTH_SHORT).show() + } + } + + binding.autoCompleteDesa.setOnClickListener{ + if (villageAdapter.count > 0){ + Log.d(TAG, "Village clicked, dropdown with ${villageAdapter.count} items") + binding.autoCompleteDesa.showDropDown() + } else { + Log.d(TAG, "No desa available") + Toast.makeText(this, "Pilih Kecamatan terlebih dahulu", Toast.LENGTH_SHORT).show() + } + } + // Set listeners for selection binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ -> val provinceId = provinceAdapter.getProvinceId(position) @@ -149,9 +176,41 @@ class AddAddressActivity : AppCompatActivity() { cityId?.let { id -> Log.d(TAG, "Setting selectedCityId=$id") + viewModel.getSubdistrict(cityId) viewModel.selectedCityId = id + binding.autoCompleteKecamatan.text.clear() + } ?: Log.e(TAG, "Could not get cityId for position $position") } + + binding.autoCompleteKecamatan.setOnItemClickListener { _, _, position, _ -> + val subdistrictId = subdistrictAdapter.getSubdistrictId(position) + val subdistrictName = subdistrictAdapter.getSubdistrictName(position) + + Log.d(TAG, "Subdistrict selected at position $position, subsId=$subdistrictId") + + subdistrictId?.let { id -> + Log.d(TAG, "Setting subdistrict id=$id") + viewModel.getVillages(subdistrictId) + viewModel.selectedSubdistrictId = id + binding.autoCompleteDesa.text.clear() + } ?: Log.e(TAG, "Could not get subsId for position $position") + + subdistrictName?.let { name -> + Log.d(TAG, "Setting subdistrict=$name") + viewModel.selectedSubdistrict = name + } ?: Log.e(TAG, "COuldnt get subs name for position ${position}") + } + + binding.autoCompleteDesa.setOnItemClickListener { _, _, position, _ -> + val villageId = villageAdapter.getVillageId(position) + Log.d(TAG, "Village selected at position $position, villageId=$villageId") + + villageId?.let { id -> + Log.d(TAG, "Setting village=$id") + viewModel.selectedVillages = id + } ?: Log.e(TAG, "Could not get villageId for position $position") + } } private fun setupButtonListeners() { @@ -175,6 +234,16 @@ class AddAddressActivity : AppCompatActivity() { handleCityState(state) } + viewModel.subdistrictState.observe(this) {state -> + Log.d(TAG, "Received subdistrictId update: $state") + handleSubdistrictState(state) + } + + viewModel.villagesState.observe(this) {state -> + Log.d(TAG, "Received subdistrictId update: $state") + handleVillageState(state) + } + // Observe address submission viewModel.addressSubmissionState.observe(this) { state -> Log.d(TAG, "Received addressSubmissionState update: $state") @@ -199,7 +268,7 @@ class AddAddressActivity : AppCompatActivity() { } is ViewState.Error -> { // Hide loading indicator - showError("Failed to load provinces: ${state.message}") +// showError("Failed to load provinces: ${state.message}") Log.e("AddAddressActivity", "Province error: ${state.message}") } } @@ -218,12 +287,50 @@ class AddAddressActivity : AppCompatActivity() { } is ViewState.Error -> { binding.cityProgressBar.visibility = View.GONE - showError("Failed to load cities: ${state.message}") +// showError("Failed to load cities: ${state.message}") Log.e("AddAddressActivity", "City error: ${state.message}") } } } + private fun handleSubdistrictState(state: com.alya.ecommerce_serang.data.repository.Result>) { + when (state) { + is Result.Loading -> { + Log.d(TAG, "Loading subdistrict...") + binding.subdistrictProgressBar.visibility = View.VISIBLE + } + is Result.Success -> { + Log.d(TAG, "Subdistrict loaded: ${state.data.size}") + binding.subdistrictProgressBar.visibility = View.GONE + subdistrictAdapter.updateData(state.data) + } + is Result.Error -> { + binding.subdistrictProgressBar.visibility = View.GONE +// showError("Failed to load subs: ${state.message}") + Log.e(TAG, "Subdistrict error: ${state}") + } + } + } + + private fun handleVillageState(state: Result>) { + when (state) { + is Result.Loading -> { + Log.d(TAG, "Loading villages...") + binding.villageProgressBar.visibility = View.VISIBLE + } + is Result.Success -> { + Log.d(TAG, "Villages loaded: ${state.data.size}") + binding.villageProgressBar.visibility = View.GONE + villageAdapter.updateData(state.data) + } + is Result.Error -> { + binding.villageProgressBar.visibility = View.GONE +// showError("Failed to load subs: ${state.message}") + Log.e(TAG, "Village error: ${state}") + } + } + } + private fun handleAddressSubmissionState(state: ViewState) { when (state) { is ViewState.Loading -> { @@ -273,7 +380,7 @@ class AddAddressActivity : AppCompatActivity() { } val street = binding.etDetailAlamat.text.toString().trim() - val subDistrict = binding.etKecamatan.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() @@ -282,9 +389,11 @@ class AddAddressActivity : AppCompatActivity() { val provinceId = viewModel.selectedProvinceId val cityId = viewModel.selectedCityId.toString() + val subDistrict = viewModel.selectedSubdistrict.toString() + val villageId = viewModel.selectedVillages.toString() Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " + - "recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, " + + "recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, subdistrict=$subDistrict, villageId=$villageId " + "lat=$latitude, long=$longitude") // Validate required fields @@ -333,7 +442,7 @@ class AddAddressActivity : AppCompatActivity() { cityId = cityId, // ⚠️ Make sure this is Int provId = provinceId, postCode = postalCode, - idVillage = "", // Or provide a real ID if needed + idVillage = villageId, // Or provide a real ID if needed detailAddress = street, isStoreLocation = false, recipient = recipient, @@ -381,8 +490,8 @@ class AddAddressActivity : AppCompatActivity() { 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() +// Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show() +// showEnableLocationDialog() return } @@ -409,7 +518,7 @@ class AddAddressActivity : AppCompatActivity() { latitude = -6.200000 longitude = 106.816666 isRequestingLocation = false - Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show() +// Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show() } }, 60000) // 15 seconds timeout @@ -423,7 +532,7 @@ class AddAddressActivity : AppCompatActivity() { 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() +// Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() return } else { Log.d(TAG, "No last known location, requesting updates") @@ -441,7 +550,7 @@ class AddAddressActivity : AppCompatActivity() { 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() +// Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() // Remove location updates after receiving a location try { @@ -464,7 +573,7 @@ class AddAddressActivity : AppCompatActivity() { binding.locationProgressBar.visibility = View.GONE binding.tvLocationStatus.text = "Provider lokasi dimatikan" isRequestingLocation = false - Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show() +// Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show() } } @@ -483,7 +592,7 @@ class AddAddressActivity : AppCompatActivity() { 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() +// Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show() // Set default location latitude = -6.200000 @@ -511,7 +620,7 @@ class AddAddressActivity : AppCompatActivity() { // 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() +// Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show() requestLocation() } } 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 d7790a1..03af39d 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 @@ -56,6 +56,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u set(value) { savedStateHandle["selectedCityId"] = value } var selectedSubdistrict: String? = null + var selectedSubdistrictId: String? = null var selectedVillages: String? = null init { @@ -104,8 +105,6 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u } } - - fun getProvinces() { _provincesState.value = ViewState.Loading viewModelScope.launch { @@ -150,18 +149,18 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u viewModelScope.launch { try { - selectedSubdistrict = cityId + selectedSubdistrictId = 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}") + Log.d(TAG, "Subdistrict loaded for city $cityId: ${it.subdistricts.size}") } ?: run { _subdistrictState.postValue(Result.Error(Exception("Failed to load cities"))) - Log.e(TAG, "City result was null for province $cityId") + Log.e(TAG, "Subdistrict result was null for city $cityId") } } catch (e: Exception) { _subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities"))) - Log.e(TAG, "Error fetching cities for province $cityId", e) + Log.e(TAG, "Error fetching subdistrict for city $cityId", e) } } } @@ -175,14 +174,14 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u val result = repository.getListVillages(subdistrictId) result?.let { _villagesState.postValue(Result.Success(it.villages)) - Log.d(TAG, "Cities loaded for province $subdistrictId: ${it.villages.size}") + Log.d(TAG, "Villages loaded for subdistrict $subdistrictId: ${it.villages.size}") } ?: run { _villagesState.postValue(Result.Error(Exception("Failed to load cities"))) - Log.e(TAG, "City result was null for province $subdistrictId") + Log.e(TAG, "Village result was null for subdistrict $subdistrictId") } } catch (e: Exception) { _villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities"))) - Log.e(TAG, "Error fetching cities for province $subdistrictId", e) + Log.e(TAG, "Error fetching villages for subdistrict $subdistrictId", e) } } } @@ -248,6 +247,12 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u addIfChanged("latitude", oldAddress.latitude, newAddress.latitude) addIfChanged("longitude", oldAddress.longitude, newAddress.longitude) addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation) + addIfChanged("village_name", oldAddress.villageName, newAddress.villageName) + addIfChanged("subdsitrict_id", oldAddress.subdistrictId, newAddress.subdistrictId) + addIfChanged("id", oldAddress.id, newAddress.id) + addIfChanged("user_id", oldAddress.userId, newAddress.userId) + addIfChanged("city_name", oldAddress.cityName, newAddress.cityName) + addIfChanged("province_name", oldAddress.provinceName, newAddress.provinceName) return params } 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 5724703..2cefa85 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 @@ -371,19 +371,35 @@ class EditAddressActivity : AppCompatActivity() { private fun createNewAddressFromInputs(oldAddress: AddressDetail): AddressDetail { val selectedProvinceId = getSelectedProvinceId() val selectedCityId = getSelectedCityId() - val selectedSubdistrictId = getSelectedSubdistrictId() + val selectedSubdistrictName = getSelectedSubdistrictName() val selectedVillageId = getSelectedVillageId() + val selectedSubdistrictId = getSelectedSubdistrictId() + val selectedVillageName = getSelectedVillageName() + val selectedProvinceName = getSelectedProvinceName() - return oldAddress.copy( + Log.d(TAG, "Old subdistrict: ${oldAddress.subdistrict}") + Log.d(TAG, "Selected subdistrictId: $selectedSubdistrictName") + + val newAddress = 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 + provinceId = selectedProvinceId?.toString() ?: oldAddress.provinceId, + cityId = selectedCityId ?: oldAddress.cityId, + subdistrict = selectedSubdistrictName ?: oldAddress.subdistrict, + villageId = selectedVillageId, + subdistrictId = selectedSubdistrictId ?: oldAddress.subdistrictId, + villageName = selectedVillageName ?: oldAddress.villageName, + provinceName = selectedProvinceName ?: oldAddress.provinceName ) + + // 🔎 Debug logs + Log.d(TAG, "New subdistrict: ${newAddress.subdistrict}") + Log.d(TAG, "New village name: ${newAddress.villageName}") + + + return newAddress } private fun getSelectedProvinceId(): Int? { @@ -392,12 +408,30 @@ class EditAddressActivity : AppCompatActivity() { return if (position >= 0) provinceAdapter.getProvinceId(position) else null } + private fun getSelectedProvinceName(): String? { + val selectedText = binding.autoCompleteProvinsi.text.toString() + val position = provincesList.indexOfFirst { it.province == selectedText } + return if (position >= 0) provinceAdapter.getProvinceName(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 getSelectedCityName(): String? { + val selectedText = binding.autoCompleteKabupaten.text.toString() + val position = citiesList.indexOfFirst { it.cityName == selectedText } + return if (position >= 0) cityAdapter.getCityName(position) else null + } + + private fun getSelectedSubdistrictName(): String? { + val selectedText = binding.autoCompleteKecamatan.text.toString() + val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText } + return if (position >= 0) subdistrictAdapter.getSubdistrictName(position) else null + } + private fun getSelectedSubdistrictId(): String? { val selectedText = binding.autoCompleteKecamatan.text.toString() val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText } @@ -410,6 +444,12 @@ class EditAddressActivity : AppCompatActivity() { return if (position >= 0) villageAdapter.getVillageId(position) else null } + private fun getSelectedVillageName(): String? { + val selectedText = binding.autoCompleteDesa.text.toString() + val position = villagesList.indexOfFirst { it.villageName == selectedText } + return if (position >= 0) villageAdapter.getVillageName(position) else null + } + companion object { const val EXTRA_ADDRESS_ID = "extra_address_id" private const val TAG = "EditAddressActivity" diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt index 82728e5..727ccb3 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt @@ -32,6 +32,10 @@ class ProvinceAdapter( fun getProvinceId(position: Int): Int? { return provinces.getOrNull(position)?.provinceId?.toIntOrNull() } + + fun getProvinceName(position: Int): String? { + return provinces.getOrNull(position)?.province?.toString() + } } class CityAdapter( @@ -53,6 +57,10 @@ class CityAdapter( fun getCityId(position: Int): String? { return cities.getOrNull(position)?.cityId?.toString() } + + fun getCityName(position: Int): String? { + return cities.getOrNull(position)?.cityName?.toString() + } } class SubdsitrictAdapter( @@ -99,6 +107,10 @@ class VillagesAdapter( fun getVillageId(position: Int): String? { return villages.getOrNull(position)?.villageId?.toString() } + + fun getVillageName(position: Int): String? { + return villages.getOrNull(position)?.villageName.toString() + } fun getPostalCode(position: Int): String?{ return villages.getOrNull(position)?.postalCode } diff --git a/app/src/main/res/layout/activity_add_address.xml b/app/src/main/res/layout/activity_add_address.xml index 986d62d..31bb2a1 100644 --- a/app/src/main/res/layout/activity_add_address.xml +++ b/app/src/main/res/layout/activity_add_address.xml @@ -168,25 +168,66 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:text="Kecamatan / Desa" + android:text="Kecamatan" android:textColor="@android:color/black" android:textSize="14sp" /> - + + - + android:textSize="14sp" /> + + + + + + + + + +