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, val subDistrict: String,
@SerializedName("city_id") @SerializedName("city_id")
val cityId: Int, val cityId: String,
@SerializedName("province_id") @SerializedName("province_id")
val provId: Int, val provId: Int,
@SerializedName("postal_code") @SerializedName("postal_code")
val postCode: String? = null, val postCode: String,
@SerializedName("detail") @SerializedName("detail")
val detailAddress: String? = null, val detailAddress: String? = null,

View File

@ -12,10 +12,8 @@ data class ListProvinceResponse(
) )
data class ProvincesItem( data class ProvincesItem(
@field:SerializedName("province")
val province: String,
@field:SerializedName("province_id") @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>, val orderItems: List<OrderListItemsItem>,
@field:SerializedName("auto_completed_at") @field:SerializedName("auto_completed_at")
val autoCompletedAt: String? = null, val autoCompletedAt: String,
@field:SerializedName("is_store_location") @field:SerializedName("is_store_location")
val isStoreLocation: Boolean? = null, val isStoreLocation: Boolean? = null,

View File

@ -15,7 +15,7 @@ data class OrderListResponse(
data class OrderItemsItem( data class OrderItemsItem(
@field:SerializedName("review_id") @field:SerializedName("review_id")
val reviewId: Int? = null, val reviewId: Int,
@field:SerializedName("quantity") @field:SerializedName("quantity")
val quantity: Int, 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.ListProvinceResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse 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.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.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.CategoryResponse import com.alya.ecommerce_serang.data.api.response.customer.product.CategoryResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreProductResponse 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.order.CompletedOrderResponse
import com.alya.ecommerce_serang.data.api.response.product.CreateSearchResponse 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.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.CreateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse 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.UpdateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse 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.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.api.response.store.review.ProductReviewResponse 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.BalanceTopUpResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
import okhttp3.MultipartBody import okhttp3.MultipartBody
@ -512,4 +514,14 @@ interface ApiService {
@GET("store/reviews") @GET("store/reviews")
suspend fun getStoreProductReview( suspend fun getStoreProductReview(
): Response<ProductReviewResponse> ): 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.auth.VerifRegisterResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse 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.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.response.customer.profile.EditProfileResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.utils.FileUtils 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 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 { suspend fun registerUser(request: RegisterRequest): RegisterResponse {
val response = apiService.register(request) // API call val response = apiService.register(request) // API call
@ -87,7 +99,7 @@ class UserRepository(private val apiService: ApiService) {
longitude: String, longitude: String,
street: String, street: String,
subdistrict: String, subdistrict: String,
cityId: Int, cityId: String,
provinceId: Int, provinceId: Int,
postalCode: Int, postalCode: Int,
detail: String, detail: String,

View File

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

View File

@ -88,7 +88,7 @@ class RegisterStep2Fragment : Fragment() {
// Update the email sent message // Update the email sent message
userData?.let { 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 // Start the resend cooldown timer
@ -119,7 +119,7 @@ class RegisterStep2Fragment : Fragment() {
Log.d(TAG, "verifyOtp called with OTP: $otp") Log.d(TAG, "verifyOtp called with OTP: $otp")
if (otp.isEmpty()) { 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 return
} }
@ -153,13 +153,13 @@ class RegisterStep2Fragment : Fragment() {
} }
is com.alya.ecommerce_serang.data.repository.Result.Success -> { is com.alya.ecommerce_serang.data.repository.Result.Success -> {
binding.progressBar.visibility = View.GONE 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() startResendCooldown()
} }
is Result.Error -> { is Result.Error -> {
Log.e(TAG, "OTP request: Error - ${result.exception.message}") Log.e(TAG, "OTP request: Error - ${result.exception.message}")
binding.progressBar.visibility = View.GONE 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 -> { else -> {
Log.d(TAG, "OTP request: Unknown state") Log.d(TAG, "OTP request: Unknown state")
@ -180,7 +180,7 @@ class RegisterStep2Fragment : Fragment() {
countDownTimer = object : CountDownTimer(30000, 1000) { countDownTimer = object : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
timeRemaining = (millisUntilFinished / 1000).toInt() 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) { if (timeRemaining % 5 == 0) {
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds") Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
} }
@ -188,7 +188,7 @@ class RegisterStep2Fragment : Fragment() {
override fun onFinish() { override fun onFinish() {
Log.d(TAG, "Cooldown finished, enabling resend button") 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.isEnabled = true
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1)) binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
timeRemaining = 0 timeRemaining = 0
@ -222,7 +222,8 @@ class RegisterStep2Fragment : Fragment() {
binding.btnVerify.isEnabled = true binding.btnVerify.isEnabled = true
// Show error message // 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 -> { else -> {
Log.d(TAG, "Registration: Unknown state") Log.d(TAG, "Registration: Unknown state")
@ -269,7 +270,7 @@ class RegisterStep2Fragment : Fragment() {
// Show error message but continue to Step 3 anyway // Show error message but continue to Step 3 anyway
Log.e(TAG, "Login failed but proceeding to Step 3", result.exception) 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 // Proceed to Step 3
(activity as? RegisterActivity)?.navigateToStep(3, null) (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.auth.RegisterActivity
import com.alya.ecommerce_serang.ui.order.address.CityAdapter 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.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.ViewState
import com.alya.ecommerce_serang.ui.order.address.VillagesAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
@ -49,6 +51,8 @@ class RegisterStep3Fragment : Fragment() {
// For province and city selection // For province and city selection
private val provinceAdapter by lazy { ProvinceAdapter(requireContext()) } private val provinceAdapter by lazy { ProvinceAdapter(requireContext()) }
private val cityAdapter by lazy { CityAdapter(requireContext()) } private val cityAdapter by lazy { CityAdapter(requireContext()) }
private val subdistrictAdapter by lazy { SubdsitrictAdapter(requireContext()) }
private val villagesAdapter by lazy { VillagesAdapter(requireContext()) }
companion object { companion object {
private const val TAG = "RegisterStep3Fragment" private const val TAG = "RegisterStep3Fragment"
@ -114,7 +118,7 @@ class RegisterStep3Fragment : Fragment() {
// Observe address submission state // Observe address submission state
observeAddressSubmissionState() observeAddressSubmissionState()
// Load provinces // Load provinces from raja ongkir
Log.d(TAG, "Requesting provinces data") Log.d(TAG, "Requesting provinces data")
registerViewModel.getProvinces() registerViewModel.getProvinces()
setupProvinceObserver() setupProvinceObserver()
@ -171,9 +175,10 @@ class RegisterStep3Fragment : Fragment() {
} }
private fun setupAutoComplete() { private fun setupAutoComplete() {
// Same implementation as before
binding.autoCompleteProvinsi.setAdapter(provinceAdapter) binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter) binding.autoCompleteKabupaten.setAdapter(cityAdapter)
binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter)
binding.autoCompleteDesa.setAdapter(villagesAdapter)
binding.autoCompleteProvinsi.setOnClickListener { binding.autoCompleteProvinsi.setOnClickListener {
binding.autoCompleteProvinsi.showDropDown() 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, _ -> binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
val provinceId = provinceAdapter.getProvinceId(position) val provinceId = provinceAdapter.getProvinceId(position)
Log.d(TAG, "Province selected at position $position, ID: $provinceId") Log.d(TAG, "Province selected at position $position, ID: $provinceId")
@ -206,13 +229,44 @@ class RegisterStep3Fragment : Fragment() {
cityId?.let { id -> cityId?.let { id ->
Log.d(TAG, "Selected city ID set to: $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() { private fun setupProvinceObserver() {
// Same implementation as before // pake raja ongkir
registerViewModel.provincesState.observe(viewLifecycleOwner) { state -> registerViewModel.provincesState.observe(viewLifecycleOwner) { state ->
when (state) { when (state) {
is ViewState.Loading -> { 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() { private fun submitAddress() {
Log.d(TAG, "submitAddress called") Log.d(TAG, "submitAddress called")
if (!validateAddressForm()) { if (!validateAddressForm()) {
@ -276,13 +368,13 @@ class RegisterStep3Fragment : Fragment() {
Log.d(TAG, "Using user ID: $userId") Log.d(TAG, "Using user ID: $userId")
val street = binding.etDetailAlamat.text.toString().trim() 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 recipient = binding.etNamaPenerima.text.toString().trim()
val phone = binding.etNomorHp.text.toString().trim() val phone = binding.etNomorHp.text.toString().trim()
val provinceId = registerViewModel.selectedProvinceId?.toInt() ?: 0 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 - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
Log.d(TAG, "Address data - Recipient: $recipient, Phone: $phone") Log.d(TAG, "Address data - Recipient: $recipient, Phone: $phone")
@ -318,13 +410,13 @@ class RegisterStep3Fragment : Fragment() {
private fun validateAddressForm(): Boolean { private fun validateAddressForm(): Boolean {
val street = binding.etDetailAlamat.text.toString().trim() 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 recipient = binding.etNamaPenerima.text.toString().trim()
val phone = binding.etNomorHp.text.toString().trim() val phone = binding.etNomorHp.text.toString().trim()
val provinceId = registerViewModel.selectedProvinceId val provinceId = registerViewModel.selectedProvinceId
val cityId = registerViewModel.selectedCityId 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 - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
Log.d(TAG, "Validating - Recipient: $recipient, Phone: $phone") Log.d(TAG, "Validating - Recipient: $recipient, Phone: $phone")
@ -409,8 +501,4 @@ class RegisterStep3Fragment : Fragment() {
ViewCompat.setWindowInsetsAnimationCallback(binding.root, null) ViewCompat.setWindowInsetsAnimationCallback(binding.root, null)
_binding = 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.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
@ -65,6 +66,16 @@ class SearchResultsAdapter(
val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store" val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store"
binding.tvStoreName.text = storeName 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 shipPrice = 0, // Will be set when user selects shipping
shipName = "", shipName = "",
shipService = "", shipService = "",
isNego = false, // Default value isNego = false, // Default value
productId = productId, productId = productId,
quantity = quantity, quantity = quantity,
shipEtd = "", shipEtd = "",

View File

@ -289,7 +289,7 @@ class AddAddressActivity : AppCompatActivity() {
val isStoreLocation = false val isStoreLocation = false
val provinceId = viewModel.selectedProvinceId val provinceId = viewModel.selectedProvinceId
val cityId = viewModel.selectedCityId val cityId = viewModel.selectedCityId.toString()
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " + 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, " +

View File

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

View File

@ -5,6 +5,8 @@ import android.util.Log
import android.widget.ArrayAdapter 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.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem 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 // UI adapters and helpers
class ProvinceAdapter( class ProvinceAdapter(
@ -12,6 +14,86 @@ class ProvinceAdapter(
resource: Int = android.R.layout.simple_dropdown_item_1line resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) { ) : 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>() private val provinces = ArrayList<ProvincesItem>()
fun updateData(newProvinces: List<ProvincesItem>) { fun updateData(newProvinces: List<ProvincesItem>) {
@ -46,7 +128,52 @@ class CityAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
fun getCityId(position: Int): Int? { fun getCityId(position: Int): String? {
return cities.getOrNull(position)?.cityId?.toIntOrNull() 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.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File import java.io.File
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -63,7 +62,6 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
private val paymentMethods = arrayOf( private val paymentMethods = arrayOf(
"Transfer Bank", "Transfer Bank",
"E-Wallet",
"QRIS", "QRIS",
) )
@ -129,7 +127,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} }
private fun setupUI() { private fun setupUI() {
val paymentMethods = listOf("Transfer Bank", "COD", "QRIS") val paymentMethods = listOf("Transfer Bank", "QRIS")
val adapter = SpinnerCardAdapter(this, paymentMethods) val adapter = SpinnerCardAdapter(this, paymentMethods)
binding.spinnerPaymentMethod.adapter = adapter binding.spinnerPaymentMethod.adapter = adapter
} }
@ -320,11 +318,12 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
return return
} }
binding.etAccountNumber.visibility = View.GONE
if (binding.etAccountNumber.text.toString().trim().isEmpty()) { // if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show()
return // return
} // }
if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") { if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()

View File

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

View File

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

View File

@ -289,18 +289,18 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}" binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}"
binding.tvPaymentDeadlineLabel.text = "Batas konfirmasi penjual:" binding.tvPaymentDeadlineLabel.text = "Batas konfirmasi penjual:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt) binding.tvPaymentDeadline.text = formatDatePaid(orders.updatedAt)
// Set buttons // cancel pesanan
binding.btnSecondary.apply { // binding.btnSecondary.apply {
visibility = View.VISIBLE // visibility = View.VISIBLE
text = "Batalkan Pesanan" // text = "Batalkan Pesanan"
setOnClickListener { // setOnClickListener {
Log.d(TAG, "Cancel Order button clicked") // Log.d(TAG, "Cancel Order button clicked")
showCancelOrderDialog(orders.orderId.toString()) // showCancelOrderDialog(orders.orderId.toString())
viewModel.getOrderDetails(orders.orderId) // viewModel.getOrderDetails(orders.orderId)
} // }
} // }
} }
"processed" -> { "processed" -> {
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for processed order") Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for processed order")
@ -309,7 +309,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Penjual sedang memproses pesanan Anda" binding.tvStatusNote.text = "Penjual sedang memproses pesanan Anda"
binding.tvPaymentDeadlineLabel.text = "Batas diproses penjual:" binding.tvPaymentDeadlineLabel.text = "Batas diproses penjual:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt) binding.tvPaymentDeadline.text = formatDateProcessed(orders.updatedAt)
binding.btnSecondary.apply { binding.btnSecondary.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -333,7 +333,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan Anda sedang dalam perjalanan. Akan sampai sekitar ${formatShipmentDate(orders.updatedAt, orders.etd ?: "0")}" binding.tvStatusNote.text = "Pesanan Anda sedang dalam perjalanan. Akan sampai sekitar ${formatShipmentDate(orders.updatedAt, orders.etd ?: "0")}"
binding.tvPaymentDeadlineLabel.text = "Estimasi pesanan sampai:" 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 { binding.btnSecondary.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -367,7 +367,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Pesanan Selesai" binding.tvStatusHeader.text = "Pesanan Selesai"
binding.tvStatusNote.visibility = View.GONE binding.tvStatusNote.visibility = View.GONE
binding.tvPaymentDeadlineLabel.text = "Pesanan selesai:" binding.tvPaymentDeadlineLabel.text = "Pesanan selesai:"
binding.tvPaymentDeadline.text = formatDate(orders.autoCompletedAt.toString()) binding.tvPaymentDeadline.text = formatDate(orders.updatedAt.toString())
binding.btnPrimary.apply { binding.btnPrimary.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -386,7 +386,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
"canceled" -> { "canceled" -> {
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for canceled order") 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.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan dibatalkan: ${orders.cancelReason ?: "Alasan tidak diberikan"}" binding.tvStatusNote.text = "Pesanan dibatalkan: ${orders.cancelReason ?: "Alasan tidak diberikan"}"
binding.tvPaymentDeadlineLabel.text = "Tanggal dibatalkan: " binding.tvPaymentDeadlineLabel.text = "Tanggal dibatalkan: "
@ -598,10 +598,6 @@ class DetailOrderStatusActivity : AppCompatActivity() {
val bottomSheet = CancelOrderBottomSheet( val bottomSheet = CancelOrderBottomSheet(
orderId = orderId, orderId = orderId,
onOrderCancelled = { onOrderCancelled = {
// Handle the successful cancellation
// Refresh the data
// Show a success message
Toast.makeText(this, "Order cancelled successfully", Toast.LENGTH_SHORT).show() 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 { private fun formatShipmentDate(dateString: String, estimateString: String): String {
Log.d(TAG, "formatShipmentDate: Formatting shipment date: $dateString with ETD: $estimateString") Log.d(TAG, "formatShipmentDate: Formatting shipment date: $dateString with ETD: $estimateString")
@ -696,7 +759,6 @@ class DetailOrderStatusActivity : AppCompatActivity() {
calendar.time = it calendar.time = it
// Add estimated days // Add estimated days
calendar.add(Calendar.DAY_OF_MONTH, estimate)
val formatted = outputFormat.format(calendar.time) val formatted = outputFormat.format(calendar.time)
Log.d(TAG, "formatShipmentDate: Estimated arrival date: $formatted") 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) orderRepository.submitComplaint(orderId.toString(), reason, imageFile)
_isSuccess.value = true _isSuccess.value = true
_message.value = "Order canceled successfully" _message.value = "Order canceled successfully"
Log.d("DetailOrderViewModel", "Complaint order success")
} catch (e: Exception) { } catch (e: Exception) {
_isSuccess.value = false _isSuccess.value = false

View File

@ -164,6 +164,7 @@ class DetailProductActivity : AppCompatActivity() {
} }
} }
//info toko
private fun updateStoreInfo(store: StoreItem?) { private fun updateStoreInfo(store: StoreItem?) {
store?.let { store?.let {
binding.tvSellerName.text = it.storeName binding.tvSellerName.text = it.storeName
@ -230,9 +231,8 @@ class DetailProductActivity : AppCompatActivity() {
private fun updateUI(product: Product){ private fun updateUI(product: Product){
binding.tvProductName.text = product.productName 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.tvSold.text = "Terjual ${product.totalSold} buah"
binding.tvRating.text = product.rating
binding.tvWeight.text = "${product.weight} gram" binding.tvWeight.text = "${product.weight} gram"
binding.tvStock.text = "${product.stock} buah" binding.tvStock.text = "${product.stock} buah"
binding.tvCategory.text = product.productCategory binding.tvCategory.text = product.productCategory
@ -243,7 +243,7 @@ class DetailProductActivity : AppCompatActivity() {
isWholesaleSelected = false // Default to regular pricing isWholesaleSelected = false // Default to regular pricing
if (isWholesaleAvailable) { if (isWholesaleAvailable) {
binding.containerWholesale.visibility = View.VISIBLE 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}" binding.descMinOrder.text = "Minimal pembelian ${minOrder}"
} else { } else {
binding.containerWholesale.visibility = View.GONE binding.containerWholesale.visibility = View.GONE
@ -281,6 +281,17 @@ class DetailProductActivity : AppCompatActivity() {
.load(fullImageUrl) .load(fullImageUrl)
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.into(binding.ivProductImage) .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) { 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) { private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
val bottomSheetDialog = BottomSheetDialog(this) val bottomSheetDialog = BottomSheetDialog(this)
val view = layoutInflater.inflate(R.layout.dialog_count_buy, null) val view = layoutInflater.inflate(R.layout.dialog_count_buy, null)
@ -377,10 +389,9 @@ class DetailProductActivity : AppCompatActivity() {
switchWholesale.visibility = View.VISIBLE switchWholesale.visibility = View.VISIBLE
Toast.makeText(this, "Minimal pembelian grosir $currentQuantity produk", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Minimal pembelian grosir $currentQuantity produk", Toast.LENGTH_SHORT).show()
} else { } else {
titleWholesale.visibility = View.GONE
switchWholesale.visibility = View.GONE switchWholesale.visibility = View.GONE
} }
// Set initial quantity based on current selection
switchWholesale.setOnCheckedChangeListener { _, isChecked -> switchWholesale.setOnCheckedChangeListener { _, isChecked ->
isWholesaleSelected = isChecked isWholesaleSelected = isChecked

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.product
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -35,7 +36,16 @@ class OtherProductAdapter (
tvProductName.text = product.name tvProductName.text = product.name
tvProductPrice.text = formatCurrency(product.price.toDouble()) 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 // Load image using Glide
Glide.with(itemView) Glide.with(itemView)

View File

@ -128,7 +128,6 @@ class StoreDetailActivity : AppCompatActivity() {
private fun updateStoreInfo(store: StoreItem?) { private fun updateStoreInfo(store: StoreItem?) {
store?.let { store?.let {
binding.tvStoreName.text = it.storeName binding.tvStoreName.text = it.storeName
binding.tvStoreRating.text = it.storeRating
binding.tvStoreLocation.text = it.storeLocation binding.tvStoreLocation.text = it.storeLocation
binding.tvStoreType.text = it.storeType binding.tvStoreType.text = it.storeType
binding.tvActiveStatus.text = it.status binding.tvActiveStatus.text = it.status
@ -145,6 +144,17 @@ class StoreDetailActivity : AppCompatActivity() {
.load(fullImageUrl) .load(fullImageUrl)
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.into(binding.ivStoreImage) .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.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat 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.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel
import androidx.core.graphics.drawable.toDrawable
import androidx.core.widget.ImageViewCompat
class RegisterStoreActivity : AppCompatActivity() { class RegisterStoreActivity : AppCompatActivity() {
@ -157,7 +156,7 @@ class RegisterStoreActivity : AppCompatActivity() {
!viewModel.bankName.value.isNullOrBlank() && !viewModel.bankName.value.isNullOrBlank() &&
(viewModel.bankNumber.value ?: 0) > 0 && (viewModel.bankNumber.value ?: 0) > 0 &&
(viewModel.provinceId.value ?: 0) > 0 && (viewModel.provinceId.value ?: 0) > 0 &&
(viewModel.cityId.value ?: 0) > 0 && !viewModel.cityId.value.isNullOrBlank() &&
(viewModel.storeTypeId.value ?: 0) > 0 && (viewModel.storeTypeId.value ?: 0) > 0 &&
viewModel.ktpUri != null && viewModel.ktpUri != null &&
viewModel.nibUri != null && viewModel.nibUri != null &&

View File

@ -42,7 +42,7 @@ class RegisterStoreViewModel(
val citiesState: LiveData<Result<List<CitiesItem>>> = _citiesState val citiesState: LiveData<Result<List<CitiesItem>>> = _citiesState
var selectedProvinceId: Int? = null var selectedProvinceId: Int? = null
var selectedCityId: Int? = null var selectedCityId: String? = null
// Form fields // Form fields
val storeName = MutableLiveData<String>() val storeName = MutableLiveData<String>()
@ -52,7 +52,7 @@ class RegisterStoreViewModel(
val longitude = MutableLiveData<String>() val longitude = MutableLiveData<String>()
val street = MutableLiveData<String>() val street = MutableLiveData<String>()
val subdistrict = MutableLiveData<String>() val subdistrict = MutableLiveData<String>()
val cityId = MutableLiveData<Int>() val cityId = MutableLiveData<String>()
val provinceId = MutableLiveData<Int>() val provinceId = MutableLiveData<Int>()
val postalCode = MutableLiveData<Int>() val postalCode = MutableLiveData<Int>()
val addressDetail = MutableLiveData<String>() val addressDetail = MutableLiveData<String>()
@ -122,7 +122,7 @@ class RegisterStoreViewModel(
longitude = longitude.value ?: "", longitude = longitude.value ?: "",
street = street.value ?: "", street = street.value ?: "",
subdistrict = subdistrict.value ?: "", subdistrict = subdistrict.value ?: "",
cityId = cityId.value ?: 0, cityId = cityId.value ?: "",
provinceId = provinceId.value ?: 0, provinceId = provinceId.value ?: 0,
postalCode = postalCode.value ?: 0, postalCode = postalCode.value ?: 0,
detail = addressDetail.value ?: "", 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.auth.VerifRegisterResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem 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.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.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.OrderRepository
@ -64,15 +66,24 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
// For address data // For address data
var selectedProvinceId: Int? = null 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>>>() private val _provincesState = MutableLiveData<ViewState<List<ProvincesItem>>>()
val provincesState: LiveData<ViewState<List<ProvincesItem>>> = _provincesState val provincesState: LiveData<ViewState<List<ProvincesItem>>> = _provincesState
private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>() private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>()
val citiesState: LiveData<ViewState<List<CitiesItem>>> = _citiesState 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 // For address submission
private val _addressSubmissionState = MutableLiveData<ViewState<String>>() private val _addressSubmissionState = MutableLiveData<ViewState<String>>()
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
@ -222,7 +233,7 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
} }
} }
} }
//using raja ongkir
fun getProvinces() { fun getProvinces() {
_provincesState.value = ViewState.Loading _provincesState.value = ViewState.Loading
viewModelScope.launch { viewModelScope.launch {
@ -242,6 +253,7 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
} }
} }
//kota pake raja ongkir
fun getCities(provinceId: Int) { fun getCities(provinceId: Int) {
_citiesState.value = ViewState.Loading _citiesState.value = ViewState.Loading
viewModelScope.launch { 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) { fun setSelectedProvinceId(id: Int) {
selectedProvinceId = id selectedProvinceId = id
} }
fun setSelectedCityId(id: Int) { fun updateSelectedCityId(id: String) {
selectedCityId = id selectedCityId = id
} }
fun updateSelectedSubdistrict(id: String){
selectedSubdistrict = id
}
fun updateSelectedVillages(id: String){
selectedVillages = id
}
fun addAddress(request: CreateAddressRequest) { fun addAddress(request: CreateAddressRequest) {
Log.d(TAG, "Starting address submission process") Log.d(TAG, "Starting address submission process")
_addressSubmissionState.value = ViewState.Loading _addressSubmissionState.value = ViewState.Loading
@ -313,6 +375,4 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
companion object { companion object {
private const val TAG = "RegisterViewModel" private const val TAG = "RegisterViewModel"
} }
//require auth
} }

View File

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

View File

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

View File

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