Compare commits

6 Commits

Author SHA1 Message Date
4d24673107 Add files via upload 2025-08-21 13:39:03 +07:00
b6b701fa3b fix add and edit address 2025-08-20 00:52:56 +07:00
94bd32d6b0 Merge remote-tracking branch 'origin/master' 2025-08-19 07:32:25 +07:00
6baf4ee5ce fix add address 2025-08-19 07:27:48 +07:00
ff8654d12a fix wholesale price and edit profile 2025-08-18 16:01:07 +07:00
22122d631b Add files via upload 2025-08-15 07:16:45 +07:00
16 changed files with 338 additions and 107 deletions

View File

@ -23,13 +23,13 @@ data class AddressDetail(
val latitude: String, val latitude: String,
@field:SerializedName("province_name") @field:SerializedName("province_name")
val provinceName: String, val provinceName: String?,
@field:SerializedName("subdistrict_id") @field:SerializedName("subdistrict_id")
val subdistrictId: String, val subdistrictId: String?,
@field:SerializedName("city_name") @field:SerializedName("city_name")
val cityName: String, val cityName: String?,
@field:SerializedName("user_id") @field:SerializedName("user_id")
val userId: Int, val userId: Int,

View File

@ -530,7 +530,7 @@ interface ApiService {
@Body request: ResetPassReq @Body request: ResetPassReq
): Response<ResetPassResponse> ): Response<ResetPassResponse>
@GET("address/detail/{id}") @GET("profile/address/detail/{id}")
suspend fun getDetailAddress( suspend fun getDetailAddress(
@Path("id") addressId: Int @Path("id") addressId: Int
): Response<AddressDetailResponse> ): Response<AddressDetailResponse>

View File

@ -151,7 +151,7 @@ class CartActivity : AppCompatActivity() {
// Debug log // Debug log
Log.d( Log.d(
TAG, TAG,
"cartItemId: ${updatedCartItem.cartItemId}, " + "cartItemId: ${updatedCartItem.cartItemId}, " +
"isWholesale: ${info.isWholesale}, " + "isWholesale: ${info.isWholesale}, " +
"wholesalePrice: $wholesalePrice, " + "wholesalePrice: $wholesalePrice, " +
@ -164,7 +164,17 @@ class CartActivity : AppCompatActivity() {
val cartItemIds = updatedItems.map { it.cartItem.cartItemId } val cartItemIds = updatedItems.map { it.cartItem.cartItemId }
val wholesaleArray = updatedItems.map { it.isWholesale }.toBooleanArray() val wholesaleArray = updatedItems.map { it.isWholesale }.toBooleanArray()
CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray) // FIX: Pass wholesale prices as IntArray
val wholesalePricesArray = updatedItems.map { info ->
if (info.isWholesale) {
val wholesalePrice = wholesalePriceMap[info.cartItem.cartItemId]
wholesalePrice?.toInt() ?: info.cartItem.price
} else {
info.cartItem.price
}
}.toIntArray()
CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray, wholesalePricesArray)
} }
private fun observeViewModel() { private fun observeViewModel() {

View File

@ -79,9 +79,6 @@ class CheckoutActivity : AppCompatActivity() {
// Determine if this is Buy Now or Cart checkout // Determine if this is Buy Now or Cart checkout
val isBuyNow = intent.hasExtra(EXTRA_PRODUCT_ID) && !intent.hasExtra(EXTRA_CART_ITEM_IDS) val isBuyNow = intent.hasExtra(EXTRA_PRODUCT_ID) && !intent.hasExtra(EXTRA_CART_ITEM_IDS)
val isWholesaleNow = intent.getBooleanExtra(EXTRA_ISWHOLESALE, false) val isWholesaleNow = intent.getBooleanExtra(EXTRA_ISWHOLESALE, false)
val wholesalePricesArray = intent.getIntArrayExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES)
if (isBuyNow) { if (isBuyNow) {
// Process Buy Now flow // Process Buy Now flow
@ -99,8 +96,7 @@ class CheckoutActivity : AppCompatActivity() {
// Process Cart checkout flow // Process Cart checkout flow
val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList() val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList()
val isWholesaleArray = intent.getBooleanArrayExtra(EXTRA_CART_ITEM_WHOLESALE) val isWholesaleArray = intent.getBooleanArrayExtra(EXTRA_CART_ITEM_WHOLESALE)
val wholesalePricesArray = intent.getIntArrayExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES)
if (cartItemIds.isNotEmpty()) { if (cartItemIds.isNotEmpty()) {
// Build map of cartItemId -> isWholesale // Build map of cartItemId -> isWholesale
@ -108,17 +104,18 @@ class CheckoutActivity : AppCompatActivity() {
cartItemIds.mapIndexed { index, id -> cartItemIds.mapIndexed { index, id ->
id to isWholesaleArray[index] id to isWholesaleArray[index]
}.toMap() }.toMap()
} else { } else {
emptyMap() emptyMap()
} }
// Build wholesalePriceMap - FIX: Map cartItemId to wholesale price
// Build wholesalePriceMap val wholesalePriceMap = if (wholesalePricesArray != null && wholesalePricesArray.size == cartItemIds.size) {
val wholesalePriceMap = cartItemIds.mapIndexed { index, id -> cartItemIds.mapIndexed { index, id ->
id to (wholesalePricesArray?.get(index) ?: 0) id to wholesalePricesArray[index]
}.toMap() }.toMap()
} else {
emptyMap()
}
viewModel.initializeFromCart( viewModel.initializeFromCart(
cartItemIds, cartItemIds,

View File

@ -96,7 +96,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
fun initializeFromCart( fun initializeFromCart(
cartItemIds: List<Int>, cartItemIds: List<Int>,
isWholesaleMap: Map<Int, Boolean> = emptyMap(), isWholesaleMap: Map<Int, Boolean> = emptyMap(),
wholesalePriceMap: Map<Int, Int> = emptyMap() // new wholesalePriceMap: Map<Int, Int> = emptyMap()
) { ) {
viewModelScope.launch { viewModelScope.launch {
_isLoading.value = true _isLoading.value = true
@ -111,13 +111,21 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
for (store in cartResult.data) { for (store in cartResult.data) {
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds } val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
if (storeItems.isNotEmpty()) { if (storeItems.isNotEmpty()) {
// ✅ Override price with wholesale price if exists // ✅ Apply wholesale prices - Replace item prices with wholesale prices
val updatedItems = storeItems.map { item -> val updatedItems = storeItems.map { item ->
val wholesalePrice = wholesalePriceMap[item.cartItemId] val wholesalePrice = wholesalePriceMap[item.cartItemId]
if (wholesalePrice != null) { val isWholesale = isWholesaleMap[item.cartItemId] ?: false
item.copy(price = wholesalePrice.toInt())
} else item // Use wholesale price if item is wholesale and price exists
if (isWholesale && wholesalePrice != null) {
Log.d(TAG, "Applying wholesale price for item ${item.cartItemId}: ${item.price} -> $wholesalePrice")
item.copy(price = wholesalePrice)
} else {
Log.d(TAG, "Using regular price for item ${item.cartItemId}: ${item.price}")
item
}
} }
matchingItems.addAll(updatedItems) matchingItems.addAll(updatedItems)
storeData = store storeData = store
break break
@ -143,16 +151,17 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
sellerName = storeData.storeName, sellerName = storeData.storeName,
sellerId = storeData.storeId, sellerId = storeData.storeId,
isBuyNow = false, isBuyNow = false,
cartItems = matchingItems, cartItems = matchingItems, // These now have updated wholesale prices
cartItemWholesaleMap = isWholesaleMap cartItemWholesaleMap = isWholesaleMap
) )
Log.d(TAG, "CheckoutData initialized: ${_checkoutData.value}")
Log.d(TAG, "Matching cart items: ${matchingItems.size}") Log.d(TAG, "CheckoutData initialized with ${matchingItems.size} items")
Log.d(TAG, "Cart items: ${matchingItems.map { it.productName to it.price }}") matchingItems.forEachIndexed { index, item ->
Log.d(TAG, "IsWholesaleMap passed: $isWholesaleMap") val isWholesale = isWholesaleMap[item.cartItemId] ?: false
Log.d(TAG, "WholesalePriceMap passed: $wholesalePriceMap") Log.d(TAG, "Item $index: ${item.productName}, Price: ${item.price}, IsWholesale: $isWholesale")
}
// Calculate totals with updated prices
calculateSubtotal() calculateSubtotal()
calculateTotal() calculateTotal()
} else { } else {
@ -163,11 +172,11 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} }
} catch (e: Exception) { } catch (e: Exception) {
_errorMessage.value = "Error: ${e.message}" _errorMessage.value = "Error: ${e.message}"
Log.e(TAG, "Error in initializeFromCart", e)
} finally { } finally {
_isLoading.value = false _isLoading.value = false
} }
} }
} }
fun getPaymentMethods() { fun getPaymentMethods() {
@ -418,8 +427,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} }
} }
companion object { companion object {
private const val TAG = "CheckoutViewModel" private const val TAG = "CheckoutViewModel"
} }

View File

@ -34,6 +34,8 @@ class SingleCartItemAdapter(private val cartItem: CartItemsItem) :
// Load placeholder image // Load placeholder image
Glide.with(ivProduct.context) Glide.with(ivProduct.context)
.load(R.drawable.placeholder_image) .load(R.drawable.placeholder_image)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(ivProduct) .into(ivProduct)
} }
} }

View File

@ -22,12 +22,14 @@ 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
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.customer.order.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
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
@ -37,8 +39,8 @@ class AddAddressActivity : AppCompatActivity() {
private lateinit var binding: ActivityAddAddressBinding private lateinit var binding: ActivityAddAddressBinding
private lateinit var apiService: ApiService private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private var profileUser: Int = 1
private lateinit var locationManager: LocationManager private lateinit var locationManager: LocationManager
private var profileUserId: Int? = null
private var isRequestingLocation = false private var isRequestingLocation = false
@ -46,6 +48,8 @@ class AddAddressActivity : AppCompatActivity() {
private var longitude: Double? = null private var longitude: Double? = null
private val provinceAdapter by lazy { ProvinceAdapter(this) } private val provinceAdapter by lazy { ProvinceAdapter(this) }
private val cityAdapter by lazy { CityAdapter(this) } private val cityAdapter by lazy { CityAdapter(this) }
private val subdistrictAdapter by lazy { SubdsitrictAdapter(this)}
private val villageAdapter by lazy { VillagesAdapter(this)}
private val viewModel: AddAddressViewModel by viewModels { private val viewModel: AddAddressViewModel by viewModels {
SavedStateViewModelFactory(this) { savedStateHandle -> SavedStateViewModelFactory(this) { savedStateHandle ->
@ -80,11 +84,15 @@ class AddAddressActivity : AppCompatActivity() {
) )
windowInsets windowInsets
} }
viewModel.loadUserProfile()
// Get user profile from session manager viewModel.userProfile.observe(this) { user ->
// profileUser =UserProfile. if (user != null) {
viewModel.userProfile.observe(this){ user -> profileUserId = user.userId
user?.let { updateProfile(it) } Log.d(TAG, "Fetched userId = $profileUserId") // ✅ debug log
} else {
Log.e(TAG, "Error get profile")
}
} }
setupToolbar() setupToolbar()
@ -94,16 +102,10 @@ class AddAddressActivity : AppCompatActivity() {
setupButtonListeners() setupButtonListeners()
setupObservers() setupObservers()
// Force trigger province loading to ensure it happens // Force trigger province loading to ensure it happens
viewModel.getProvinces() viewModel.getProvinces()
} }
private fun updateProfile(userProfile: UserProfile){
profileUser = userProfile.userId
}
// UI setup methods // UI setup methods
private fun setupToolbar() { private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener { binding.toolbar.setNavigationOnClickListener {
@ -116,6 +118,8 @@ class AddAddressActivity : AppCompatActivity() {
// Set adapters // Set adapters
binding.autoCompleteProvinsi.setAdapter(provinceAdapter) binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter) binding.autoCompleteKabupaten.setAdapter(cityAdapter)
binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter)
binding.autoCompleteDesa.setAdapter(villageAdapter)
// Make dropdown appear on click (not just when typing) // Make dropdown appear on click (not just when typing)
binding.autoCompleteProvinsi.setOnClickListener { binding.autoCompleteProvinsi.setOnClickListener {
@ -134,6 +138,26 @@ class AddAddressActivity : AppCompatActivity() {
} }
} }
binding.autoCompleteKecamatan.setOnClickListener{
if (subdistrictAdapter.count > 0){
Log.d(TAG, "Subdistrict clicked, dropdown with ${subdistrictAdapter.count} items")
binding.autoCompleteKecamatan.showDropDown()
} else {
Log.d(TAG, "No kecamatan available")
Toast.makeText(this, "Pilih Kabupaten / Kota terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
binding.autoCompleteDesa.setOnClickListener{
if (villageAdapter.count > 0){
Log.d(TAG, "Village clicked, dropdown with ${villageAdapter.count} items")
binding.autoCompleteDesa.showDropDown()
} else {
Log.d(TAG, "No desa available")
Toast.makeText(this, "Pilih Kecamatan terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
// Set listeners for selection // Set listeners for selection
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ -> binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
val provinceId = provinceAdapter.getProvinceId(position) val provinceId = provinceAdapter.getProvinceId(position)
@ -152,9 +176,41 @@ class AddAddressActivity : AppCompatActivity() {
cityId?.let { id -> cityId?.let { id ->
Log.d(TAG, "Setting selectedCityId=$id") Log.d(TAG, "Setting selectedCityId=$id")
viewModel.getSubdistrict(cityId)
viewModel.selectedCityId = id viewModel.selectedCityId = id
binding.autoCompleteKecamatan.text.clear()
} ?: Log.e(TAG, "Could not get cityId for position $position") } ?: Log.e(TAG, "Could not get cityId for position $position")
} }
binding.autoCompleteKecamatan.setOnItemClickListener { _, _, position, _ ->
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
val subdistrictName = subdistrictAdapter.getSubdistrictName(position)
Log.d(TAG, "Subdistrict selected at position $position, subsId=$subdistrictId")
subdistrictId?.let { id ->
Log.d(TAG, "Setting subdistrict id=$id")
viewModel.getVillages(subdistrictId)
viewModel.selectedSubdistrictId = id
binding.autoCompleteDesa.text.clear()
} ?: Log.e(TAG, "Could not get subsId for position $position")
subdistrictName?.let { name ->
Log.d(TAG, "Setting subdistrict=$name")
viewModel.selectedSubdistrict = name
} ?: Log.e(TAG, "COuldnt get subs name for position ${position}")
}
binding.autoCompleteDesa.setOnItemClickListener { _, _, position, _ ->
val villageId = villageAdapter.getVillageId(position)
Log.d(TAG, "Village selected at position $position, villageId=$villageId")
villageId?.let { id ->
Log.d(TAG, "Setting village=$id")
viewModel.selectedVillages = id
} ?: Log.e(TAG, "Could not get villageId for position $position")
}
} }
private fun setupButtonListeners() { private fun setupButtonListeners() {
@ -178,6 +234,16 @@ class AddAddressActivity : AppCompatActivity() {
handleCityState(state) handleCityState(state)
} }
viewModel.subdistrictState.observe(this) {state ->
Log.d(TAG, "Received subdistrictId update: $state")
handleSubdistrictState(state)
}
viewModel.villagesState.observe(this) {state ->
Log.d(TAG, "Received subdistrictId update: $state")
handleVillageState(state)
}
// Observe address submission // Observe address submission
viewModel.addressSubmissionState.observe(this) { state -> viewModel.addressSubmissionState.observe(this) { state ->
Log.d(TAG, "Received addressSubmissionState update: $state") Log.d(TAG, "Received addressSubmissionState update: $state")
@ -202,7 +268,7 @@ class AddAddressActivity : AppCompatActivity() {
} }
is ViewState.Error -> { is ViewState.Error -> {
// Hide loading indicator // Hide loading indicator
showError("Failed to load provinces: ${state.message}") // showError("Failed to load provinces: ${state.message}")
Log.e("AddAddressActivity", "Province error: ${state.message}") Log.e("AddAddressActivity", "Province error: ${state.message}")
} }
} }
@ -221,12 +287,50 @@ class AddAddressActivity : AppCompatActivity() {
} }
is ViewState.Error -> { is ViewState.Error -> {
binding.cityProgressBar.visibility = View.GONE binding.cityProgressBar.visibility = View.GONE
showError("Failed to load cities: ${state.message}") // showError("Failed to load cities: ${state.message}")
Log.e("AddAddressActivity", "City error: ${state.message}") Log.e("AddAddressActivity", "City error: ${state.message}")
} }
} }
} }
private fun handleSubdistrictState(state: com.alya.ecommerce_serang.data.repository.Result<List<SubdistrictsItem>>) {
when (state) {
is Result.Loading -> {
Log.d(TAG, "Loading subdistrict...")
binding.subdistrictProgressBar.visibility = View.VISIBLE
}
is Result.Success -> {
Log.d(TAG, "Subdistrict loaded: ${state.data.size}")
binding.subdistrictProgressBar.visibility = View.GONE
subdistrictAdapter.updateData(state.data)
}
is Result.Error -> {
binding.subdistrictProgressBar.visibility = View.GONE
// showError("Failed to load subs: ${state.message}")
Log.e(TAG, "Subdistrict error: ${state}")
}
}
}
private fun handleVillageState(state: Result<List<VillagesItem>>) {
when (state) {
is Result.Loading -> {
Log.d(TAG, "Loading villages...")
binding.villageProgressBar.visibility = View.VISIBLE
}
is Result.Success -> {
Log.d(TAG, "Villages loaded: ${state.data.size}")
binding.villageProgressBar.visibility = View.GONE
villageAdapter.updateData(state.data)
}
is Result.Error -> {
binding.villageProgressBar.visibility = View.GONE
// showError("Failed to load subs: ${state.message}")
Log.e(TAG, "Village error: ${state}")
}
}
}
private fun handleAddressSubmissionState(state: ViewState<String>) { private fun handleAddressSubmissionState(state: ViewState<String>) {
when (state) { when (state) {
is ViewState.Loading -> { is ViewState.Loading -> {
@ -276,23 +380,20 @@ class AddAddressActivity : AppCompatActivity() {
} }
val street = binding.etDetailAlamat.text.toString().trim() val street = binding.etDetailAlamat.text.toString().trim()
val subDistrict = binding.etKecamatan.text.toString().trim() // val subDistrict = binding.etKecamatan.text.toString().trim()
val postalCode = binding.etKodePos.text.toString().trim() val 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 userId = try { val userId = profileUserId
profileUser
} catch (e: Exception) {
Log.w(TAG, "Error getting userId, using default", e)
1 // Default userId for testing
}
val isStoreLocation = false val isStoreLocation = false
val provinceId = viewModel.selectedProvinceId val provinceId = viewModel.selectedProvinceId
val cityId = viewModel.selectedCityId.toString() val cityId = viewModel.selectedCityId.toString()
val subDistrict = viewModel.selectedSubdistrict.toString()
val villageId = viewModel.selectedVillages.toString()
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " + Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " +
"recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, " + "recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, subdistrict=$subDistrict, villageId=$villageId " +
"lat=$latitude, long=$longitude") "lat=$latitude, long=$longitude")
// Validate required fields // Validate required fields
@ -333,7 +434,7 @@ class AddAddressActivity : AppCompatActivity() {
// Create request with all fields // Create request with all fields
val request = CreateAddressRequest( val request = CreateAddressRequest(
userId = userId, userId = userId!!,
lat = latitude!!, // Safe to use !! as we've checked above lat = latitude!!, // Safe to use !! as we've checked above
long = longitude!!, long = longitude!!,
street = street, street = street,
@ -341,7 +442,7 @@ class AddAddressActivity : AppCompatActivity() {
cityId = cityId, // ⚠️ Make sure this is Int cityId = cityId, // ⚠️ Make sure this is Int
provId = provinceId, provId = provinceId,
postCode = postalCode, postCode = postalCode,
idVillage = "", // Or provide a real ID if needed idVillage = villageId, // Or provide a real ID if needed
detailAddress = street, detailAddress = street,
isStoreLocation = false, isStoreLocation = false,
recipient = recipient, recipient = recipient,
@ -389,8 +490,8 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Provider lokasi tidak tersedia" binding.tvLocationStatus.text = "Provider lokasi tidak tersedia"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
showEnableLocationDialog() // showEnableLocationDialog()
return return
} }
@ -417,7 +518,7 @@ class AddAddressActivity : AppCompatActivity() {
latitude = -6.200000 latitude = -6.200000
longitude = 106.816666 longitude = 106.816666
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show()
} }
}, 60000) // 15 seconds timeout }, 60000) // 15 seconds timeout
@ -431,7 +532,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Lokasi terdeteksi: ${lastLocation.latitude}, ${lastLocation.longitude}" binding.tvLocationStatus.text = "Lokasi terdeteksi: ${lastLocation.latitude}, ${lastLocation.longitude}"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show()
return return
} else { } else {
Log.d(TAG, "No last known location, requesting updates") Log.d(TAG, "No last known location, requesting updates")
@ -449,7 +550,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Lokasi terdeteksi: ${location.latitude}, ${location.longitude}" binding.tvLocationStatus.text = "Lokasi terdeteksi: ${location.latitude}, ${location.longitude}"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() // Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show()
// Remove location updates after receiving a location // Remove location updates after receiving a location
try { try {
@ -472,7 +573,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Provider lokasi dimatikan" binding.tvLocationStatus.text = "Provider lokasi dimatikan"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show() // Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show()
} }
} }
@ -491,7 +592,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Error: ${e.message}" binding.tvLocationStatus.text = "Error: ${e.message}"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show()
// Set default location // Set default location
latitude = -6.200000 latitude = -6.200000
@ -519,12 +620,12 @@ class AddAddressActivity : AppCompatActivity() {
// Add button to reload location (add this button to your layout) // Add button to reload location (add this button to your layout)
binding.btnReloadLocation.setOnClickListener { binding.btnReloadLocation.setOnClickListener {
Log.d(TAG, "Reload location button clicked") Log.d(TAG, "Reload location button clicked")
Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show()
requestLocation() requestLocation()
} }
} }
companion object { companion object {
private const val TAG = "AddAddressViewModel" private const val TAG = "AddAddressActivity"
} }
} }

View File

@ -56,6 +56,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
set(value) { savedStateHandle["selectedCityId"] = value } set(value) { savedStateHandle["selectedCityId"] = value }
var selectedSubdistrict: String? = null var selectedSubdistrict: String? = null
var selectedSubdistrictId: String? = null
var selectedVillages: String? = null var selectedVillages: String? = null
init { init {
@ -104,8 +105,6 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
} }
} }
fun getProvinces() { fun getProvinces() {
_provincesState.value = ViewState.Loading _provincesState.value = ViewState.Loading
viewModelScope.launch { viewModelScope.launch {
@ -150,18 +149,18 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
viewModelScope.launch { viewModelScope.launch {
try { try {
selectedSubdistrict = cityId selectedSubdistrictId = cityId
val result = repository.getListSubdistrict(cityId) val result = repository.getListSubdistrict(cityId)
result?.let { result?.let {
_subdistrictState.postValue(Result.Success(it.subdistricts)) _subdistrictState.postValue(Result.Success(it.subdistricts))
Log.d(TAG, "Cities loaded for province $cityId: ${it.subdistricts.size}") Log.d(TAG, "Subdistrict loaded for city $cityId: ${it.subdistricts.size}")
} ?: run { } ?: run {
_subdistrictState.postValue(Result.Error(Exception("Failed to load cities"))) _subdistrictState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "City result was null for province $cityId") Log.e(TAG, "Subdistrict result was null for city $cityId")
} }
} catch (e: Exception) { } catch (e: Exception) {
_subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities"))) _subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching cities for province $cityId", e) Log.e(TAG, "Error fetching subdistrict for city $cityId", e)
} }
} }
} }
@ -175,14 +174,14 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
val result = repository.getListVillages(subdistrictId) val result = repository.getListVillages(subdistrictId)
result?.let { result?.let {
_villagesState.postValue(Result.Success(it.villages)) _villagesState.postValue(Result.Success(it.villages))
Log.d(TAG, "Cities loaded for province $subdistrictId: ${it.villages.size}") Log.d(TAG, "Villages loaded for subdistrict $subdistrictId: ${it.villages.size}")
} ?: run { } ?: run {
_villagesState.postValue(Result.Error(Exception("Failed to load cities"))) _villagesState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "City result was null for province $subdistrictId") Log.e(TAG, "Village result was null for subdistrict $subdistrictId")
} }
} catch (e: Exception) { } catch (e: Exception) {
_villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities"))) _villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching cities for province $subdistrictId", e) Log.e(TAG, "Error fetching villages for subdistrict $subdistrictId", e)
} }
} }
} }
@ -248,10 +247,34 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
addIfChanged("latitude", oldAddress.latitude, newAddress.latitude) addIfChanged("latitude", oldAddress.latitude, newAddress.latitude)
addIfChanged("longitude", oldAddress.longitude, newAddress.longitude) addIfChanged("longitude", oldAddress.longitude, newAddress.longitude)
addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation) addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation)
addIfChanged("village_name", oldAddress.villageName, newAddress.villageName)
addIfChanged("subdsitrict_id", oldAddress.subdistrictId, newAddress.subdistrictId)
addIfChanged("id", oldAddress.id, newAddress.id)
addIfChanged("user_id", oldAddress.userId, newAddress.userId)
addIfChanged("city_name", oldAddress.cityName, newAddress.cityName)
addIfChanged("province_name", oldAddress.provinceName, newAddress.provinceName)
return params return params
} }
fun loadUserProfile() {
viewModelScope.launch {
when (val result = repository.fetchUserProfile()) {
is Result.Success -> {
result.data?.let {
_userProfile.postValue(it) // send UserProfile to LiveData
} ?: _errorMessageUser.postValue("User data not found")
}
is Result.Error -> {
_errorMessageUser.postValue(result.exception.message ?: "Unknown error")
}
is Result.Loading -> {
null
}
}
}
}
companion object { companion object {
private const val TAG = "AddAddressViewModel" private const val TAG = "AddAddressViewModel"
} }

View File

@ -371,19 +371,35 @@ class EditAddressActivity : AppCompatActivity() {
private fun createNewAddressFromInputs(oldAddress: AddressDetail): AddressDetail { private fun createNewAddressFromInputs(oldAddress: AddressDetail): AddressDetail {
val selectedProvinceId = getSelectedProvinceId() val selectedProvinceId = getSelectedProvinceId()
val selectedCityId = getSelectedCityId() val selectedCityId = getSelectedCityId()
val selectedSubdistrictId = getSelectedSubdistrictId() val selectedSubdistrictName = getSelectedSubdistrictName()
val selectedVillageId = getSelectedVillageId() val selectedVillageId = getSelectedVillageId()
val selectedSubdistrictId = getSelectedSubdistrictId()
val selectedVillageName = getSelectedVillageName()
val selectedProvinceName = getSelectedProvinceName()
return oldAddress.copy( Log.d(TAG, "Old subdistrict: ${oldAddress.subdistrict}")
Log.d(TAG, "Selected subdistrictId: $selectedSubdistrictName")
val newAddress = oldAddress.copy(
recipient = binding.etNamaPenerima.text.toString().trim(), recipient = binding.etNamaPenerima.text.toString().trim(),
phone = binding.etNomorHp.text.toString().trim(), phone = binding.etNomorHp.text.toString().trim(),
detail = binding.etDetailAlamat.text.toString().trim(), detail = binding.etDetailAlamat.text.toString().trim(),
postalCode = binding.etKodePos.text.toString().trim(), postalCode = binding.etKodePos.text.toString().trim(),
provinceId = selectedProvinceId.toString(), provinceId = selectedProvinceId?.toString() ?: oldAddress.provinceId,
cityId = selectedCityId.toString(), cityId = selectedCityId ?: oldAddress.cityId,
subdistrict = selectedSubdistrictId.toString(), subdistrict = selectedSubdistrictName ?: oldAddress.subdistrict,
villageId = selectedVillageId villageId = selectedVillageId,
subdistrictId = selectedSubdistrictId ?: oldAddress.subdistrictId,
villageName = selectedVillageName ?: oldAddress.villageName,
provinceName = selectedProvinceName ?: oldAddress.provinceName
) )
// 🔎 Debug logs
Log.d(TAG, "New subdistrict: ${newAddress.subdistrict}")
Log.d(TAG, "New village name: ${newAddress.villageName}")
return newAddress
} }
private fun getSelectedProvinceId(): Int? { private fun getSelectedProvinceId(): Int? {
@ -392,12 +408,30 @@ class EditAddressActivity : AppCompatActivity() {
return if (position >= 0) provinceAdapter.getProvinceId(position) else null return if (position >= 0) provinceAdapter.getProvinceId(position) else null
} }
private fun getSelectedProvinceName(): String? {
val selectedText = binding.autoCompleteProvinsi.text.toString()
val position = provincesList.indexOfFirst { it.province == selectedText }
return if (position >= 0) provinceAdapter.getProvinceName(position) else null
}
private fun getSelectedCityId(): String? { private fun getSelectedCityId(): String? {
val selectedText = binding.autoCompleteKabupaten.text.toString() val selectedText = binding.autoCompleteKabupaten.text.toString()
val position = citiesList.indexOfFirst { it.cityName == selectedText } val position = citiesList.indexOfFirst { it.cityName == selectedText }
return if (position >= 0) cityAdapter.getCityId(position) else null return if (position >= 0) cityAdapter.getCityId(position) else null
} }
private fun getSelectedCityName(): String? {
val selectedText = binding.autoCompleteKabupaten.text.toString()
val position = citiesList.indexOfFirst { it.cityName == selectedText }
return if (position >= 0) cityAdapter.getCityName(position) else null
}
private fun getSelectedSubdistrictName(): String? {
val selectedText = binding.autoCompleteKecamatan.text.toString()
val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText }
return if (position >= 0) subdistrictAdapter.getSubdistrictName(position) else null
}
private fun getSelectedSubdistrictId(): String? { private fun getSelectedSubdistrictId(): String? {
val selectedText = binding.autoCompleteKecamatan.text.toString() val selectedText = binding.autoCompleteKecamatan.text.toString()
val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText } val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText }
@ -410,6 +444,12 @@ class EditAddressActivity : AppCompatActivity() {
return if (position >= 0) villageAdapter.getVillageId(position) else null return if (position >= 0) villageAdapter.getVillageId(position) else null
} }
private fun getSelectedVillageName(): String? {
val selectedText = binding.autoCompleteDesa.text.toString()
val position = villagesList.indexOfFirst { it.villageName == selectedText }
return if (position >= 0) villageAdapter.getVillageName(position) else null
}
companion object { companion object {
const val EXTRA_ADDRESS_ID = "extra_address_id" const val EXTRA_ADDRESS_ID = "extra_address_id"
private const val TAG = "EditAddressActivity" private const val TAG = "EditAddressActivity"

View File

@ -32,6 +32,10 @@ class ProvinceAdapter(
fun getProvinceId(position: Int): Int? { fun getProvinceId(position: Int): Int? {
return provinces.getOrNull(position)?.provinceId?.toIntOrNull() return provinces.getOrNull(position)?.provinceId?.toIntOrNull()
} }
fun getProvinceName(position: Int): String? {
return provinces.getOrNull(position)?.province?.toString()
}
} }
class CityAdapter( class CityAdapter(
@ -53,6 +57,10 @@ class CityAdapter(
fun getCityId(position: Int): String? { fun getCityId(position: Int): String? {
return cities.getOrNull(position)?.cityId?.toString() return cities.getOrNull(position)?.cityId?.toString()
} }
fun getCityName(position: Int): String? {
return cities.getOrNull(position)?.cityName?.toString()
}
} }
class SubdsitrictAdapter( class SubdsitrictAdapter(
@ -99,6 +107,10 @@ class VillagesAdapter(
fun getVillageId(position: Int): String? { fun getVillageId(position: Int): String? {
return villages.getOrNull(position)?.villageId?.toString() return villages.getOrNull(position)?.villageId?.toString()
} }
fun getVillageName(position: Int): String? {
return villages.getOrNull(position)?.villageName.toString()
}
fun getPostalCode(position: Int): String?{ fun getPostalCode(position: Int): String?{
return villages.getOrNull(position)?.postalCode return villages.getOrNull(position)?.postalCode
} }

View File

@ -1,10 +1,12 @@
package com.alya.ecommerce_serang.ui.profile.editprofile package com.alya.ecommerce_serang.ui.profile.editprofile
import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
@ -159,17 +161,14 @@ class EditProfileCustActivity : AppCompatActivity() {
} }
private fun openImagePicker() { private fun openImagePicker() {
// Check for permission first val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission( Manifest.permission.READ_MEDIA_IMAGES
this, } else {
android.Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED }
) {
ActivityCompat.requestPermissions( if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
this, ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_STORAGE_PERMISSION)
arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
REQUEST_STORAGE_PERMISSION
)
} else { } else {
launchImagePicker() launchImagePicker()
} }

View File

@ -17,6 +17,7 @@ import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.DetailShipmen
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.gson.Gson import com.google.gson.Gson
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
@ -96,7 +97,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull() val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}" tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = formatPrice(product?.price.toString()) tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toInt()) }
val fullImageUrl = when (val img = product?.productImage) { val fullImageUrl = when (val img = product?.productImage) {
is String -> { is String -> {
@ -169,7 +170,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull() val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}" tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = formatPrice(product?.price.toString()) tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toInt()) }
val fullImageUrl = when (val img = product?.productImage) { val fullImageUrl = when (val img = product?.productImage) {
is String -> { is String -> {
@ -185,7 +186,7 @@ class SellsAdapter(
.into(ivSellsProduct) .into(ivSellsProduct)
tvSellsQty.text = "${order.orderItems?.size} produk" tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsPrice.text = formatPrice(order.totalAmount.toString()) tvSellsPrice.text = order.totalAmount?.let { formatPrice(it.toInt()) }
} }
"paid" -> { "paid" -> {
layoutOrders.visibility = View.GONE layoutOrders.visibility = View.GONE
@ -308,10 +309,9 @@ class SellsAdapter(
} }
} }
private fun formatPrice(price: String): String { private fun formatPrice(amount: Int): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0 val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
val formattedPrice = String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble) return formatter.format(amount.toLong()).replace(",00", "")
return formattedPrice
} }
} }
} }

View File

@ -168,25 +168,66 @@
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" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
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" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText <AutoCompleteTextView
android:id="@+id/etKecamatan" 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/subdistrictProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="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"
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/villageProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -38,7 +38,6 @@
android:src="@drawable/baseline_edit_24" android:src="@drawable/baseline_edit_24"
android:focusable="true" android:focusable="true"
android:clickable="true" android:clickable="true"
android:visibility="gone"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>

BIN
pasarKlik.apk Normal file

Binary file not shown.

BIN
unduh/PasarKlik_v2.apk Normal file

Binary file not shown.