update province cities subdistricts and villages

This commit is contained in:
shaulascr
2025-07-02 01:40:41 +07:00
parent db38654159
commit 331410eb98
30 changed files with 628 additions and 123 deletions

View File

@ -16,12 +16,12 @@ data class CreateAddressRequest (
val subDistrict: String,
@SerializedName("city_id")
val cityId: Int,
val cityId: String,
@SerializedName("province_id")
val provId: Int,
@SerializedName("postal_code")
val postCode: String? = null,
val postCode: String,
@SerializedName("detail")
val detailAddress: String? = null,

View File

@ -12,10 +12,8 @@ data class ListProvinceResponse(
)
data class ProvincesItem(
@field:SerializedName("province")
val province: String,
@field:SerializedName("province_id")
val provinceId: String
val provinceId: String,
@field:SerializedName("province")
val province: String
)

View File

@ -56,7 +56,7 @@ data class Orders(
val orderItems: List<OrderListItemsItem>,
@field:SerializedName("auto_completed_at")
val autoCompletedAt: String? = null,
val autoCompletedAt: String,
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean? = null,

View File

@ -15,7 +15,7 @@ data class OrderListResponse(
data class OrderItemsItem(
@field:SerializedName("review_id")
val reviewId: Int? = null,
val reviewId: Int,
@field:SerializedName("quantity")
val quantity: Int,

View File

@ -0,0 +1,21 @@
package com.alya.ecommerce_serang.data.api.response.customer.order
import com.google.gson.annotations.SerializedName
data class SubdistrictResponse(
@field:SerializedName("subdistricts")
val subdistricts: List<SubdistrictsItem>,
@field:SerializedName("message")
val message: String
)
data class SubdistrictsItem(
@field:SerializedName("subdistrict_id")
val subdistrictId: String,
@field:SerializedName("subdistrict_name")
val subdistrictName: String
)

View File

@ -0,0 +1,24 @@
package com.alya.ecommerce_serang.data.api.response.customer.order
import com.google.gson.annotations.SerializedName
data class VillagesResponse(
@field:SerializedName("villages")
val villages: List<VillagesItem>,
@field:SerializedName("message")
val message: String
)
data class VillagesItem(
@field:SerializedName("village_id")
val villageId: String,
@field:SerializedName("village_name")
val villageName: String,
@field:SerializedName("postal_code")
val postalCode: String
)

View File

@ -53,6 +53,8 @@ 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.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.CategoryResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreProductResponse
@ -68,14 +70,14 @@ import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
import com.alya.ecommerce_serang.data.api.response.product.CreateSearchResponse
import com.alya.ecommerce_serang.data.api.response.product.SearchHistoryResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse
import com.alya.ecommerce_serang.data.api.response.store.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
import com.alya.ecommerce_serang.data.api.response.store.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.api.response.store.review.ProductReviewResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
import okhttp3.MultipartBody
@ -512,4 +514,14 @@ interface ApiService {
@GET("store/reviews")
suspend fun getStoreProductReview(
): Response<ProductReviewResponse>
@GET("subdistrict/{cityId}")
suspend fun getSubdistrict(
@Path("cityId") cityId: String
): Response<SubdistrictResponse>
@GET("villages/{subdistrictId}")
suspend fun getVillages(
@Path("subdistrictId") subdistrictId: String
): Response<VillagesResponse>
}

View File

@ -21,6 +21,8 @@ import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
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.profile.EditProfileResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.utils.FileUtils
@ -68,6 +70,16 @@ class UserRepository(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 registerUser(request: RegisterRequest): RegisterResponse {
val response = apiService.register(request) // API call
@ -87,7 +99,7 @@ class UserRepository(private val apiService: ApiService) {
longitude: String,
street: String,
subdistrict: String,
cityId: Int,
cityId: String,
provinceId: Int,
postalCode: Int,
detail: String,

View File

@ -105,6 +105,7 @@ class LoginActivity : AppCompatActivity() {
finish()
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e("LoginActivity", "Login Failed: ${result.exception.message}")
Toast.makeText(this, "Login Failed: ${result.exception.message}", Toast.LENGTH_LONG).show()
}
is Result.Loading -> {

View File

@ -88,7 +88,7 @@ class RegisterStep2Fragment : Fragment() {
// Update the email sent message
userData?.let {
binding.tvEmailSent.text = "We've sent a verification code to ${it.email}"
binding.tvEmailSent.text = "Kami telah mengirimkan kode OTP ke alamat email ${it.email}. Silahkan periksa email anda."
}
// Start the resend cooldown timer
@ -119,7 +119,7 @@ class RegisterStep2Fragment : Fragment() {
Log.d(TAG, "verifyOtp called with OTP: $otp")
if (otp.isEmpty()) {
Toast.makeText(requireContext(), "Please enter the verification code", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Masukkan kode OTP anda", Toast.LENGTH_SHORT).show()
return
}
@ -153,13 +153,13 @@ class RegisterStep2Fragment : Fragment() {
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
binding.progressBar.visibility = View.GONE
Toast.makeText(requireContext(), "Verification code resent", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Kode OTP sudah dikirim", Toast.LENGTH_SHORT).show()
startResendCooldown()
}
is Result.Error -> {
Log.e(TAG, "OTP request: Error - ${result.exception.message}")
binding.progressBar.visibility = View.GONE
Toast.makeText(requireContext(), "Failed to resend code: ${result.exception.message}", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Gagal mengirim kode OTP", Toast.LENGTH_SHORT).show()
}
else -> {
Log.d(TAG, "OTP request: Unknown state")
@ -180,7 +180,7 @@ class RegisterStep2Fragment : Fragment() {
countDownTimer = object : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) {
timeRemaining = (millisUntilFinished / 1000).toInt()
binding.tvTimer.text = "Resend available in 00:${String.format("%02d", timeRemaining)}"
binding.tvTimer.text = "Kirim ulang OTP dalam waktu 00:${String.format("%02d", timeRemaining)}"
if (timeRemaining % 5 == 0) {
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
}
@ -188,7 +188,7 @@ class RegisterStep2Fragment : Fragment() {
override fun onFinish() {
Log.d(TAG, "Cooldown finished, enabling resend button")
binding.tvTimer.text = "You can now resend the code"
binding.tvTimer.text = "Dapat mengirim ulang kode OTP"
binding.tvResendOtp.isEnabled = true
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
timeRemaining = 0
@ -222,7 +222,8 @@ class RegisterStep2Fragment : Fragment() {
binding.btnVerify.isEnabled = true
// Show error message
Toast.makeText(requireContext(), "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Registration Failed: ${result.exception.message}")
Toast.makeText(requireContext(), "Gagal melakukan regsitrasi", Toast.LENGTH_SHORT).show()
}
else -> {
Log.d(TAG, "Registration: Unknown state")
@ -269,7 +270,7 @@ class RegisterStep2Fragment : Fragment() {
// Show error message but continue to Step 3 anyway
Log.e(TAG, "Login failed but proceeding to Step 3", result.exception)
Toast.makeText(requireContext(), "Note: Auto-login failed, but registration was successful", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Gagal login, namun berhasil membuat akun", Toast.LENGTH_SHORT).show()
// Proceed to Step 3
(activity as? RegisterActivity)?.navigateToStep(3, null)

View File

@ -23,7 +23,9 @@ import com.alya.ecommerce_serang.ui.auth.LoginActivity
import com.alya.ecommerce_serang.ui.auth.RegisterActivity
import com.alya.ecommerce_serang.ui.order.address.CityAdapter
import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter
import com.alya.ecommerce_serang.ui.order.address.SubdsitrictAdapter
import com.alya.ecommerce_serang.ui.order.address.ViewState
import com.alya.ecommerce_serang.ui.order.address.VillagesAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
@ -49,6 +51,8 @@ class RegisterStep3Fragment : Fragment() {
// For province and city selection
private val provinceAdapter by lazy { ProvinceAdapter(requireContext()) }
private val cityAdapter by lazy { CityAdapter(requireContext()) }
private val subdistrictAdapter by lazy { SubdsitrictAdapter(requireContext()) }
private val villagesAdapter by lazy { VillagesAdapter(requireContext()) }
companion object {
private const val TAG = "RegisterStep3Fragment"
@ -114,7 +118,7 @@ class RegisterStep3Fragment : Fragment() {
// Observe address submission state
observeAddressSubmissionState()
// Load provinces
// Load provinces from raja ongkir
Log.d(TAG, "Requesting provinces data")
registerViewModel.getProvinces()
setupProvinceObserver()
@ -171,9 +175,10 @@ class RegisterStep3Fragment : Fragment() {
}
private fun setupAutoComplete() {
// Same implementation as before
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter)
binding.autoCompleteDesa.setAdapter(villagesAdapter)
binding.autoCompleteProvinsi.setOnClickListener {
binding.autoCompleteProvinsi.showDropDown()
@ -188,6 +193,24 @@ class RegisterStep3Fragment : Fragment() {
}
}
binding.autoCompleteKecamatan.setOnClickListener {
if (subdistrictAdapter.count > 0) {
Log.d(TAG, "Subdistrict dropdown clicked, showing ${subdistrictAdapter.count} cities")
binding.autoCompleteKecamatan.showDropDown()
} else {
Toast.makeText(requireContext(), "Pilih kabupaten / kota terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
binding.autoCompleteDesa.setOnClickListener {
if (villagesAdapter.count > 0) {
Log.d(TAG, "Village dropdown clicked, showing ${villagesAdapter.count} cities")
binding.autoCompleteDesa.showDropDown()
} else {
Toast.makeText(requireContext(), "Pilih kecamatan terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
val provinceId = provinceAdapter.getProvinceId(position)
Log.d(TAG, "Province selected at position $position, ID: $provinceId")
@ -206,13 +229,44 @@ class RegisterStep3Fragment : Fragment() {
cityId?.let { id ->
Log.d(TAG, "Selected city ID set to: $id")
registerViewModel.selectedCityId = id
registerViewModel.updateSelectedCityId(id)
registerViewModel.getSubdistrict(id)
binding.autoCompleteKecamatan.text.clear()
}
}
binding.autoCompleteKecamatan.setOnItemClickListener{ _, _, position, _ ->
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
Log.d(TAG, "Subdistrict selected at position $position, ID: $subdistrictId")
subdistrictId?.let { id ->
Log.d(TAG, "Selected subdistrict ID set to: $id")
registerViewModel.selectedSubdistrict = id
registerViewModel.getVillages(id)
binding.autoCompleteDesa.text.clear()
}
}
binding.autoCompleteDesa.setOnItemClickListener{ _, _, position, _ ->
val villageId = villagesAdapter.getVillageId(position)
val postalCode = villagesAdapter.getPostalCode(position)
Log.d(TAG, "Village selected at position $position, ID: $villageId")
villageId?.let { id ->
Log.d(TAG, "Selected village ID set to: $id")
registerViewModel.selectedVillages = id
}
postalCode?.let { postCode ->
registerViewModel.selectedPostalCode = postCode
}
binding.etKodePos.setText(registerViewModel.selectedPostalCode ?: "")
}
}
private fun setupProvinceObserver() {
// Same implementation as before
// pake raja ongkir
registerViewModel.provincesState.observe(viewLifecycleOwner) { state ->
when (state) {
is ViewState.Loading -> {
@ -256,8 +310,46 @@ class RegisterStep3Fragment : Fragment() {
}
}
}
registerViewModel.subdistrictState.observe(viewLifecycleOwner) { state ->
when (state) {
is ViewState.Loading -> {
binding.progressBarKecamatan.visibility = View.VISIBLE
}
is ViewState.Success -> {
Log.d(TAG, "Subdistrict: Success - received ${state.data.size} kecamatan")
binding.progressBarKecamatan.visibility = View.GONE
subdistrictAdapter.updateData(state.data)
Log.d(TAG, "Updated subdistrict adapter with ${state.data.size} items")
}
is ViewState.Error -> {
Log.e(TAG, "Subdistrict: Error - ${state.message}")
binding.progressBarKecamatan.visibility = View.GONE
showError("Failed to load kecamatan: ${state.message}")
}
}
}
registerViewModel.villagesState.observe(viewLifecycleOwner) { state ->
when (state) {
is ViewState.Loading -> {
binding.progressBarDesa.visibility = View.VISIBLE
}
is ViewState.Success -> {
Log.d(TAG, "Village: Success - received ${state.data.size} desa")
binding.progressBarDesa.visibility = View.GONE
villagesAdapter.updateData(state.data)
Log.d(TAG, "Updated village adapter with ${state.data.size} items")
}
is ViewState.Error -> {
Log.e(TAG, "Village: Error - ${state.message}")
binding.progressBarDesa.visibility = View.GONE
showError("Failed to load desa: ${state.message}")
}
}
}
}
private fun submitAddress() {
Log.d(TAG, "submitAddress called")
if (!validateAddressForm()) {
@ -276,13 +368,13 @@ class RegisterStep3Fragment : Fragment() {
Log.d(TAG, "Using user ID: $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 provinceId = registerViewModel.selectedProvinceId?.toInt() ?: 0
val cityId = registerViewModel.selectedCityId?.toInt() ?: 0
val cityId = registerViewModel.selectedCityId.toString()
val subDistrict = registerViewModel.selectedSubdistrict.toString()
val postalCode = registerViewModel.selectedPostalCode.toString()
Log.d(TAG, "Address data - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
Log.d(TAG, "Address data - Recipient: $recipient, Phone: $phone")
@ -318,13 +410,13 @@ class RegisterStep3Fragment : Fragment() {
private fun validateAddressForm(): Boolean {
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 provinceId = registerViewModel.selectedProvinceId
val cityId = registerViewModel.selectedCityId
val subDistrict = registerViewModel.selectedSubdistrict.toString()
val postalCode = registerViewModel.selectedPostalCode
Log.d(TAG, "Validating - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
Log.d(TAG, "Validating - Recipient: $recipient, Phone: $phone")
@ -409,8 +501,4 @@ class RegisterStep3Fragment : Fragment() {
ViewCompat.setWindowInsetsAnimationCallback(binding.root, null)
_binding = null
}
//
// // Data classes for province and city
// data class Province(val id: String, val name: String)
// data class City(val id: String, val name: String)
}

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -65,6 +66,16 @@ class SearchResultsAdapter(
val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store"
binding.tvStoreName.text = storeName
val ratingStr = product.rating
val ratingValue = ratingStr?.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) {
binding.rating.text = String.format("%.1f", ratingValue)
binding.rating.visibility = View.VISIBLE
} else {
binding.rating.text = "Belum ada rating"
binding.rating.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
}
}

View File

@ -64,7 +64,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
shipPrice = 0, // Will be set when user selects shipping
shipName = "",
shipService = "",
isNego = false, // Default value
isNego = false, // Default value
productId = productId,
quantity = quantity,
shipEtd = "",

View File

@ -289,7 +289,7 @@ class AddAddressActivity : AppCompatActivity() {
val isStoreLocation = false
val provinceId = viewModel.selectedProvinceId
val cityId = viewModel.selectedCityId
val cityId = viewModel.selectedCityId.toString()
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " +
"recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, " +

View File

@ -36,8 +36,8 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
get() = savedStateHandle.get<Int>("selectedProvinceId")
set(value) { savedStateHandle["selectedProvinceId"] = value }
var selectedCityId: Int?
get() = savedStateHandle.get<Int>("selectedCityId")
var selectedCityId: String?
get() = savedStateHandle.get<String>("selectedCityId")
set(value) { savedStateHandle["selectedCityId"] = value }
init {
@ -129,7 +129,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
selectedProvinceId = id
}
fun setSelectedCityId(id: Int) {
fun updateSelectedCityId(id: String) {
selectedCityId = id
}

View File

@ -5,6 +5,8 @@ import android.util.Log
import android.widget.ArrayAdapter
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
// UI adapters and helpers
class ProvinceAdapter(
@ -12,6 +14,86 @@ class ProvinceAdapter(
resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) {
// companion object {
// private const val TAG = "ProvinceAdapter"
// }
//
// // --- Static list of provinces ---------------------------------------------------------------
// private val provinces = listOf(
// ProvincesItem(1, "Aceh"),
// ProvincesItem(2, "Sumatera Utara"),
// ProvincesItem(3, "Sumatera Barat"),
// ProvincesItem(4, "Riau"),
// ProvincesItem(5, "Kepulauan Riau"),
// ProvincesItem(6, "Jambi"),
// ProvincesItem(7, "Sumatera Selatan"),
// ProvincesItem(8, "Kepulauan Bangka Belitung"),
// ProvincesItem(9, "Bengkulu"),
// ProvincesItem(10, "Lampung"),
// ProvincesItem(11, "DKI Jakarta"),
// ProvincesItem(12, "Jawa Barat"),
// ProvincesItem(13, "Banten"),
// ProvincesItem(14, "Jawa Tengah"),
// ProvincesItem(15, "Daerah Istimewa Yogyakarta"),
// ProvincesItem(16, "Jawa Timur"),
// ProvincesItem(17, "Bali"),
// ProvincesItem(18, "Nusa Tenggara Barat"),
// ProvincesItem(19, "Nusa Tenggara Timur"),
// ProvincesItem(20, "Kalimantan Barat"),
// ProvincesItem(21, "Kalimantan Tengah"),
// ProvincesItem(22, "Kalimantan Selatan"),
// ProvincesItem(23, "Kalimantan Timur"),
// ProvincesItem(24, "Kalimantan Utara"),
// ProvincesItem(25, "Sulawesi Utara"),
// ProvincesItem(26, "Gorontalo"),
// ProvincesItem(27, "Sulawesi Tengah"),
// ProvincesItem(28, "Sulawesi Barat"),
// ProvincesItem(29, "Sulawesi Selatan"),
// ProvincesItem(30, "Sulawesi Tenggara"),
// ProvincesItem(31, "Maluku"),
// ProvincesItem(32, "Maluku Utara"),
// ProvincesItem(33, "Papua Barat"),
// ProvincesItem(34, "Papua"),
// ProvincesItem(35, "Papua Tengah"),
// ProvincesItem(36, "Papua Pegunungan"),
// ProvincesItem(37, "Papua Selatan"),
// ProvincesItem(38, "Papua Barat Daya")
// )
//
// // --- Init block -----------------------------------------------------------------------------
// init {
// addAll(getProvinceNames()) // prepopulate adapter
// Log.d(TAG, "Adapter created with ${count} provinces")
// }
//
// // --- Public helper functions ----------------------------------------------------------------
// fun updateData(newProvinces: List<ProvincesItem>) {
// // If you actually want to replace the list, comment this line
// // provinces = newProvinces // (make `provinces` var instead of val)
//
// clear()
// addAll(newProvinces.map { it.province })
// notifyDataSetChanged()
//
// Log.d(TAG, "updateData(): updated with ${newProvinces.size} provinces")
// }
//
// fun getProvinceId(position: Int): Int? {
// val id = provinces.getOrNull(position)?.provinceId
// Log.d(TAG, "getProvinceId(): position=$position, id=$id")
// return id
// }
//
// fun getProvinceItem(position: Int): ProvincesItem? {
// val item = provinces.getOrNull(position)
// Log.d(TAG, "getProvinceItem(): position=$position, item=$item")
// return item
// }
//
// // --- Private helpers ------------------------------------------------------------------------
// private fun getProvinceNames(): List<String> = provinces.map { it.province }
//call from endpoint
private val provinces = ArrayList<ProvincesItem>()
fun updateData(newProvinces: List<ProvincesItem>) {
@ -46,7 +128,52 @@ class CityAdapter(
notifyDataSetChanged()
}
fun getCityId(position: Int): Int? {
return cities.getOrNull(position)?.cityId?.toIntOrNull()
fun getCityId(position: Int): String? {
return cities.getOrNull(position)?.cityId?.toString()
}
}
class SubdsitrictAdapter(
context: Context,
resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) {
private val cities = ArrayList<SubdistrictsItem>()
fun updateData(newCities: List<SubdistrictsItem>) {
cities.clear()
cities.addAll(newCities)
clear()
addAll(cities.map { it.subdistrictName })
notifyDataSetChanged()
}
fun getSubdistrictId(position: Int): String? {
return cities.getOrNull(position)?.subdistrictId?.toString()
}
}
class VillagesAdapter(
context: Context,
resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) {
private val villages = ArrayList<VillagesItem>()
fun updateData(newCities: List<VillagesItem>) {
villages.clear()
villages.addAll(newCities)
clear()
addAll(villages.map { it.villageName })
notifyDataSetChanged()
}
fun getVillageId(position: Int): String? {
return villages.getOrNull(position)?.villageId?.toString()
}
fun getPostalCode(position: Int): String?{
return villages.getOrNull(position)?.postalCode
}
}

View File

@ -39,7 +39,6 @@ import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -63,7 +62,6 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
private val paymentMethods = arrayOf(
"Transfer Bank",
"E-Wallet",
"QRIS",
)
@ -129,7 +127,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
}
private fun setupUI() {
val paymentMethods = listOf("Transfer Bank", "COD", "QRIS")
val paymentMethods = listOf("Transfer Bank", "QRIS")
val adapter = SpinnerCardAdapter(this, paymentMethods)
binding.spinnerPaymentMethod.adapter = adapter
}
@ -320,11 +318,12 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
return
}
binding.etAccountNumber.visibility = View.GONE
if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show()
return
}
// if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
// Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show()
// return
// }
if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()

View File

@ -213,14 +213,15 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE
text = itemView.context.getString(R.string.dl_processed)
}
btnLeft.apply {
visibility = View.VISIBLE
text = itemView.context.getString(R.string.canceled_order_btn)
setOnClickListener {
showCancelOrderDialog(order.orderId.toString())
viewModel.refreshOrders()
}
}
// gabisa complaint
// btnLeft.apply {
// visibility = View.VISIBLE
// text = itemView.context.getString(R.string.canceled_order_btn)
// setOnClickListener {
// showCancelOrderDialog(order.orderId.toString())
// viewModel.refreshOrders()
// }
// }
}
"shipped" -> {
// Untuk status shipped, tampilkan "Lacak Pengiriman" dan "Terima Barang"
@ -268,13 +269,21 @@ class OrderHistoryAdapter(
text = itemView.context.getString(R.string.dl_shipped)
}
btnRight.apply {
visibility = View.VISIBLE
text = itemView.context.getString(R.string.add_review)
setOnClickListener {
addReviewProduct(order)
viewModel.refreshOrders()
// Handle click event
val checkReview = order.orderItems[0].reviewId
if (checkReview > 0){
visibility = View.VISIBLE
text = itemView.context.getString(R.string.add_review)
setOnClickListener {
addReviewProduct(order)
// viewModel.refreshOrders()
// Handle click event
}
} else {
visibility = View.GONE
}
}
deadlineDate.apply {
visibility = View.VISIBLE
@ -518,7 +527,7 @@ class OrderHistoryAdapter(
}
}
// Create and show the bottom sheet using the obtained FragmentManager
// cancel sebelum bayar
val bottomSheet = CancelOrderBottomSheet(
orderId = orderId,
onOrderCancelled = {
@ -531,6 +540,7 @@ class OrderHistoryAdapter(
bottomSheet.show(fragmentActivity.supportFragmentManager, CancelOrderBottomSheet.TAG)
}
// tambah review / ulasan
private fun addReviewProduct(order: OrdersItem) {
// Use ViewModel to fetch order details
viewModel.getOrderDetails(order.orderId)
@ -550,7 +560,7 @@ class OrderHistoryAdapter(
}
}
// Observe the order details result
// Observe order items
viewModel.orderItems.observe(itemView.findViewTreeLifecycleOwner()!!) { orderItems ->
if (orderItems != null && orderItems.isNotEmpty()) {
// For single item review

View File

@ -55,7 +55,7 @@ class CancelOrderBottomSheet(
val btnConfirm = view.findViewById<Button>(R.id.btn_confirm)
// Set the title
tvTitle.text = "Cancel Order #$orderId"
tvTitle.text = "Batalkan Pesanan #$orderId"
// Set up the spinner with cancellation reasons
setupReasonSpinner(spinnerReason)
@ -94,11 +94,11 @@ class CancelOrderBottomSheet(
private fun getCancellationReasons(): List<CancelOrderReq> {
// These should ideally come from the server or a configuration
return listOf(
CancelOrderReq(1, "Changed my mind"),
CancelOrderReq(2, "Found a better option"),
CancelOrderReq(3, "Ordered by mistake"),
CancelOrderReq(4, "Delivery time too long"),
CancelOrderReq(5, "Other reason")
CancelOrderReq(1, "Berubah pikiran"),
CancelOrderReq(2, "Menemukan pilihan yang lebih baik"),
CancelOrderReq(3, "Kesalahan pemesanan"),
CancelOrderReq(4, "Waktu pengiriman lama"),
CancelOrderReq(5, "Lainnya")
)
}

View File

@ -289,18 +289,18 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}"
binding.tvPaymentDeadlineLabel.text = "Batas konfirmasi penjual:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt)
binding.tvPaymentDeadline.text = formatDatePaid(orders.updatedAt)
// Set buttons
binding.btnSecondary.apply {
visibility = View.VISIBLE
text = "Batalkan Pesanan"
setOnClickListener {
Log.d(TAG, "Cancel Order button clicked")
showCancelOrderDialog(orders.orderId.toString())
viewModel.getOrderDetails(orders.orderId)
}
}
// cancel pesanan
// binding.btnSecondary.apply {
// visibility = View.VISIBLE
// text = "Batalkan Pesanan"
// setOnClickListener {
// Log.d(TAG, "Cancel Order button clicked")
// showCancelOrderDialog(orders.orderId.toString())
// viewModel.getOrderDetails(orders.orderId)
// }
// }
}
"processed" -> {
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for processed order")
@ -309,7 +309,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Penjual sedang memproses pesanan Anda"
binding.tvPaymentDeadlineLabel.text = "Batas diproses penjual:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt)
binding.tvPaymentDeadline.text = formatDateProcessed(orders.updatedAt)
binding.btnSecondary.apply {
visibility = View.VISIBLE
@ -333,7 +333,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan Anda sedang dalam perjalanan. Akan sampai sekitar ${formatShipmentDate(orders.updatedAt, orders.etd ?: "0")}"
binding.tvPaymentDeadlineLabel.text = "Estimasi pesanan sampai:"
binding.tvPaymentDeadline.text = formatShipmentDate(orders.updatedAt, orders.etd ?: "0")
binding.tvPaymentDeadline.text = formatShipmentDate(orders.autoCompletedAt, orders.etd ?: "0")
binding.btnSecondary.apply {
visibility = View.VISIBLE
@ -367,7 +367,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Pesanan Selesai"
binding.tvStatusNote.visibility = View.GONE
binding.tvPaymentDeadlineLabel.text = "Pesanan selesai:"
binding.tvPaymentDeadline.text = formatDate(orders.autoCompletedAt.toString())
binding.tvPaymentDeadline.text = formatDate(orders.updatedAt.toString())
binding.btnPrimary.apply {
visibility = View.VISIBLE
@ -386,7 +386,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
"canceled" -> {
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for canceled order")
binding.tvStatusHeader.text = "Pesanan Selesai"
binding.tvStatusHeader.text = "Pesanan Dibatalkan"
binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan dibatalkan: ${orders.cancelReason ?: "Alasan tidak diberikan"}"
binding.tvPaymentDeadlineLabel.text = "Tanggal dibatalkan: "
@ -598,10 +598,6 @@ class DetailOrderStatusActivity : AppCompatActivity() {
val bottomSheet = CancelOrderBottomSheet(
orderId = orderId,
onOrderCancelled = {
// Handle the successful cancellation
// Refresh the data
// Show a success message
Toast.makeText(this, "Order cancelled successfully", Toast.LENGTH_SHORT).show()
}
)
@ -673,6 +669,73 @@ class DetailOrderStatusActivity : AppCompatActivity() {
}
}
private fun formatDatePaid(dateString: String): String {
Log.d(TAG, "formatDatePay: Formatting payment date: $dateString")
return try {
// Parse the ISO 8601 date
val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
val createdDate = isoDateFormat.parse(dateString)
// Add 24 hours to get due date
val calendar = Calendar.getInstance()
calendar.time = createdDate
calendar.add(Calendar.HOUR, 120)
val dueDate = calendar.time
val timeFormat = SimpleDateFormat("HH:mm", Locale("id", "ID"))
val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale("id", "ID"))
val timePart = timeFormat.format(dueDate)
val datePart = dateFormat.format(dueDate)
val formatted = "$timePart\n$datePart"
Log.d(TAG, "formatDatePay: Formatted payment date: $formatted")
formatted
} catch (e: Exception) {
Log.e(TAG, "formatDatePay: Error formatting date: ${e.message}", e)
dateString
}
}
//format batas tgl diproses
private fun formatDateProcessed(dateString: String): String {
Log.d(TAG, "formatDatePay: Formatting payment date: $dateString")
return try {
// Parse the ISO 8601 date
val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
val createdDate = isoDateFormat.parse(dateString)
// Add 24 hours to get due date
val calendar = Calendar.getInstance()
calendar.time = createdDate
calendar.add(Calendar.HOUR, 72)
val dueDate = calendar.time
val timeFormat = SimpleDateFormat("HH:mm", Locale("id", "ID"))
val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale("id", "ID"))
val timePart = timeFormat.format(dueDate)
val datePart = dateFormat.format(dueDate)
val formatted = "$timePart\n$datePart"
Log.d(TAG, "formatDatePay: Formatted payment date: $formatted")
formatted
} catch (e: Exception) {
Log.e(TAG, "formatDatePay: Error formatting date: ${e.message}", e)
dateString
}
}
private fun formatShipmentDate(dateString: String, estimateString: String): String {
Log.d(TAG, "formatShipmentDate: Formatting shipment date: $dateString with ETD: $estimateString")
@ -696,7 +759,6 @@ class DetailOrderStatusActivity : AppCompatActivity() {
calendar.time = it
// Add estimated days
calendar.add(Calendar.DAY_OF_MONTH, estimate)
val formatted = outputFormat.format(calendar.time)
Log.d(TAG, "formatShipmentDate: Estimated arrival date: $formatted")

View File

@ -75,6 +75,7 @@ class DetailOrderViewModel(private val orderRepository: OrderRepository): ViewMo
orderRepository.submitComplaint(orderId.toString(), reason, imageFile)
_isSuccess.value = true
_message.value = "Order canceled successfully"
Log.d("DetailOrderViewModel", "Complaint order success")
} catch (e: Exception) {
_isSuccess.value = false

View File

@ -164,6 +164,7 @@ class DetailProductActivity : AppCompatActivity() {
}
}
//info toko
private fun updateStoreInfo(store: StoreItem?) {
store?.let {
binding.tvSellerName.text = it.storeName
@ -230,9 +231,8 @@ class DetailProductActivity : AppCompatActivity() {
private fun updateUI(product: Product){
binding.tvProductName.text = product.productName
binding.tvPrice.text = "Rp${formatCurrency(product.price.toDouble())}"
binding.tvPrice.text = formatCurrency(product.price.toDouble())
binding.tvSold.text = "Terjual ${product.totalSold} buah"
binding.tvRating.text = product.rating
binding.tvWeight.text = "${product.weight} gram"
binding.tvStock.text = "${product.stock} buah"
binding.tvCategory.text = product.productCategory
@ -243,7 +243,7 @@ class DetailProductActivity : AppCompatActivity() {
isWholesaleSelected = false // Default to regular pricing
if (isWholesaleAvailable) {
binding.containerWholesale.visibility = View.VISIBLE
binding.tvPriceWholesale.text = "Rp${formatCurrency(product.wholesalePrice!!.toDouble())}"
binding.tvPriceWholesale.text = formatCurrency(product.wholesalePrice!!.toDouble())
binding.descMinOrder.text = "Minimal pembelian ${minOrder}"
} else {
binding.containerWholesale.visibility = View.GONE
@ -281,6 +281,17 @@ class DetailProductActivity : AppCompatActivity() {
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(binding.ivProductImage)
val ratingStr = product.rating
val ratingValue = ratingStr?.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) {
binding.tvRating.text = String.format("%.1f", ratingValue)
binding.tvRating.visibility = View.VISIBLE
} else {
binding.tvRating.text = "Belum ada rating"
binding.tvRating.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
}
private fun handleAllReviewsClick(productId: Int) {
@ -347,6 +358,7 @@ class DetailProductActivity : AppCompatActivity() {
}
//dialog tambah quantity dan harga grosir
private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
val bottomSheetDialog = BottomSheetDialog(this)
val view = layoutInflater.inflate(R.layout.dialog_count_buy, null)
@ -377,10 +389,9 @@ class DetailProductActivity : AppCompatActivity() {
switchWholesale.visibility = View.VISIBLE
Toast.makeText(this, "Minimal pembelian grosir $currentQuantity produk", Toast.LENGTH_SHORT).show()
} else {
titleWholesale.visibility = View.GONE
switchWholesale.visibility = View.GONE
}
// Set initial quantity based on current selection
switchWholesale.setOnCheckedChangeListener { _, isChecked ->
isWholesaleSelected = isChecked

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.product
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
@ -35,7 +36,16 @@ class OtherProductAdapter (
tvProductName.text = product.name
tvProductPrice.text = formatCurrency(product.price.toDouble())
rating.text = product.rating
val ratingStr = product.rating
val ratingValue = ratingStr?.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) {
binding.rating.text = String.format("%.1f", ratingValue)
binding.rating.visibility = View.VISIBLE
} else {
binding.rating.text = "Belum ada rating"
binding.rating.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
// Load image using Glide
Glide.with(itemView)

View File

@ -128,7 +128,6 @@ class StoreDetailActivity : AppCompatActivity() {
private fun updateStoreInfo(store: StoreItem?) {
store?.let {
binding.tvStoreName.text = it.storeName
binding.tvStoreRating.text = it.storeRating
binding.tvStoreLocation.text = it.storeLocation
binding.tvStoreType.text = it.storeType
binding.tvActiveStatus.text = it.status
@ -145,6 +144,17 @@ class StoreDetailActivity : AppCompatActivity() {
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(binding.ivStoreImage)
val ratingStr = it.storeRating
val ratingValue = ratingStr?.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) {
binding.tvStoreRating.text = String.format("%.1f", ratingValue)
binding.tvStoreRating.visibility = View.VISIBLE
} else {
binding.tvStoreRating.text = "Belum ada rating"
binding.tvStoreRating.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
}
}

View File

@ -22,6 +22,7 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@ -36,8 +37,6 @@ import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel
import androidx.core.graphics.drawable.toDrawable
import androidx.core.widget.ImageViewCompat
class RegisterStoreActivity : AppCompatActivity() {
@ -157,7 +156,7 @@ class RegisterStoreActivity : AppCompatActivity() {
!viewModel.bankName.value.isNullOrBlank() &&
(viewModel.bankNumber.value ?: 0) > 0 &&
(viewModel.provinceId.value ?: 0) > 0 &&
(viewModel.cityId.value ?: 0) > 0 &&
!viewModel.cityId.value.isNullOrBlank() &&
(viewModel.storeTypeId.value ?: 0) > 0 &&
viewModel.ktpUri != null &&
viewModel.nibUri != null &&

View File

@ -42,7 +42,7 @@ class RegisterStoreViewModel(
val citiesState: LiveData<Result<List<CitiesItem>>> = _citiesState
var selectedProvinceId: Int? = null
var selectedCityId: Int? = null
var selectedCityId: String? = null
// Form fields
val storeName = MutableLiveData<String>()
@ -52,7 +52,7 @@ class RegisterStoreViewModel(
val longitude = MutableLiveData<String>()
val street = MutableLiveData<String>()
val subdistrict = MutableLiveData<String>()
val cityId = MutableLiveData<Int>()
val cityId = MutableLiveData<String>()
val provinceId = MutableLiveData<Int>()
val postalCode = MutableLiveData<Int>()
val addressDetail = MutableLiveData<String>()
@ -122,7 +122,7 @@ class RegisterStoreViewModel(
longitude = longitude.value ?: "",
street = street.value ?: "",
subdistrict = subdistrict.value ?: "",
cityId = cityId.value ?: 0,
cityId = cityId.value ?: "",
provinceId = provinceId.value ?: 0,
postalCode = postalCode.value ?: 0,
detail = addressDetail.value ?: "",

View File

@ -16,6 +16,8 @@ import com.alya.ecommerce_serang.data.api.response.auth.User
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
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
@ -64,15 +66,24 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
// For address data
var selectedProvinceId: Int? = null
var selectedCityId: Int? = null
var selectedCityId: String? = null
var selectedSubdistrict: String? = null
var selectedVillages: String? = null
var selectedPostalCode: String? = null
// For provinces and cities
// For provinces and cities using raja ongkir
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
private val _subdistrictState = MutableLiveData<ViewState<List<SubdistrictsItem>>>()
val subdistrictState: LiveData<ViewState<List<SubdistrictsItem>>> = _subdistrictState
private val _villagesState = MutableLiveData<ViewState<List<VillagesItem>>>()
val villagesState: LiveData<ViewState<List<VillagesItem>>> = _villagesState
// For address submission
private val _addressSubmissionState = MutableLiveData<ViewState<String>>()
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
@ -222,7 +233,7 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
}
}
}
//using raja ongkir
fun getProvinces() {
_provincesState.value = ViewState.Loading
viewModelScope.launch {
@ -242,6 +253,7 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
}
}
//kota pake raja ongkir
fun getCities(provinceId: Int) {
_citiesState.value = ViewState.Loading
viewModelScope.launch {
@ -263,14 +275,64 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
}
}
fun getSubdistrict(cityId: String) {
_subdistrictState.value = ViewState.Loading
viewModelScope.launch {
try {
selectedSubdistrict = cityId
val result = repository.getListSubdistrict(cityId)
result?.let {
_subdistrictState.postValue(ViewState.Success(it.subdistricts))
Log.d(TAG, "Cities loaded for province $cityId: ${it.subdistricts.size}")
} ?: run {
_subdistrictState.postValue(ViewState.Error("Failed to load cities"))
Log.e(TAG, "City result was null for province $cityId")
}
} catch (e: Exception) {
_subdistrictState.postValue(ViewState.Error(e.message ?: "Error loading cities"))
Log.e(TAG, "Error fetching cities for province $cityId", e)
}
}
}
fun getVillages(subdistrictId: String) {
_villagesState.value = ViewState.Loading
viewModelScope.launch {
try {
selectedVillages = subdistrictId
val result = repository.getListVillages(subdistrictId)
result?.let {
_villagesState.postValue(ViewState.Success(it.villages))
Log.d(TAG, "Cities loaded for province $subdistrictId: ${it.villages.size}")
} ?: run {
_villagesState.postValue(ViewState.Error("Failed to load cities"))
Log.e(TAG, "City result was null for province $subdistrictId")
}
} catch (e: Exception) {
_villagesState.postValue(ViewState.Error(e.message ?: "Error loading cities"))
Log.e(TAG, "Error fetching cities for province $subdistrictId", e)
}
}
}
fun setSelectedProvinceId(id: Int) {
selectedProvinceId = id
}
fun setSelectedCityId(id: Int) {
fun updateSelectedCityId(id: String) {
selectedCityId = id
}
fun updateSelectedSubdistrict(id: String){
selectedSubdistrict = id
}
fun updateSelectedVillages(id: String){
selectedVillages = id
}
fun addAddress(request: CreateAddressRequest) {
Log.d(TAG, "Starting address submission process")
_addressSubmissionState.value = ViewState.Loading
@ -313,6 +375,4 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
companion object {
private const val TAG = "RegisterViewModel"
}
//require auth
}

View File

@ -133,6 +133,7 @@
android:layout_marginTop="16dp"
android:text="Nomor Rekening / Nomor HP *"
android:fontFamily="@font/dmsans_semibold"
android:visibility="gone"
android:textSize="16sp" />
<EditText
@ -145,6 +146,7 @@
android:inputType="text"
android:minHeight="50dp"
android:textSize="14sp"
android:visibility="gone"
android:padding="12dp" />
<TextView
@ -152,6 +154,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Tanggal Pembayaran *"
android:visibility="gone"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" />
@ -164,6 +167,7 @@
android:drawableEnd="@drawable/ic_calendar"
android:drawablePadding="8dp"
android:hint="Pilih tanggal"
android:visibility="gone"
android:minHeight="50dp"
android:padding="12dp" />

View File

@ -5,6 +5,7 @@
android:layout_height="match_parent">
<ScrollView
android:id="@+id/sv_address_register"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/btn_register"
@ -149,7 +150,7 @@
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" />
@ -157,18 +158,61 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Isi Kecamatan / Desa"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
android:hint="Pilih Kecamatan"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_kecamatan"
<AutoCompleteTextView
android:id="@+id/autoCompleteKecamatan"
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"
android:inputType="textCapWords" />
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/progress_bar_kecamatan"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:visibility="gone" />
<!-- DESA / Kelurahan -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kelurahan / Desa"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Pilih Kelurahan / Desa"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteDesa"
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/progress_bar_desa"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:visibility="gone" />
<!-- Kode Pos -->
<TextView
android:layout_width="wrap_content"
@ -196,7 +240,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="@drawable/bg_button_outline"
android:text="Previous"
android:text="Kembali"
android:textAllCaps="false"
android:textColor="@color/blue1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
@ -214,7 +258,8 @@
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/sv_address_register"/>
<ProgressBar
android:id="@+id/progress_bar"

View File

@ -118,12 +118,11 @@
<!-- Cancellation Reasons -->
<string-array name="cancellation_reasons">
<item>Found a better price elsewhere</item>
<item>Changed my mind about the product</item>
<item>Ordered the wrong item</item>
<item>Shipping time is too long</item>
<item>Financial reasons</item>
<item>Other reason</item>
<item>Menemukan harga yang lebih baik</item>
<item>Berubah pikiran dengan pilihan produk</item>
<item>Kesalahan membeli produk</item>
<item>Alasan keuangan</item>
<item>Lainnya</item>
</string-array>
<!-- Chat Activity -->