Compare commits

8 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
6efbcde784 fix 2025-08-15 05:29:54 +07:00
a6d5d10e78 fix address, edit address 2025-08-15 02:11:16 +07:00
21 changed files with 1232 additions and 119 deletions

View File

@ -82,7 +82,8 @@ data class Product(
data class CartItemCheckoutInfo( data class CartItemCheckoutInfo(
val cartItem: CartItemsItem, val cartItem: CartItemsItem,
val isWholesale: Boolean val isWholesale: Boolean,
val wholesalePrice: Int? = null
) )

View File

@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
data class AddressDetailResponse( data class AddressDetailResponse(
@field:SerializedName("address") @field:SerializedName("address")
val address: Address, val address: AddressDetail,
@field:SerializedName("message") @field:SerializedName("message")
val message: String val message: String
@ -14,7 +14,7 @@ data class AddressDetailResponse(
data class AddressDetail( data class AddressDetail(
@field:SerializedName("village_id") @field:SerializedName("village_id")
val villageId: String, val villageId: String?,
@field:SerializedName("is_store_location") @field:SerializedName("is_store_location")
val isStoreLocation: Boolean, val isStoreLocation: Boolean,
@ -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,
@ -38,7 +38,7 @@ data class AddressDetail(
val provinceId: String, val provinceId: String,
@field:SerializedName("phone") @field:SerializedName("phone")
val phone: String, val phone: String?,
@field:SerializedName("street") @field:SerializedName("street")
val street: String, val street: String,
@ -47,7 +47,7 @@ data class AddressDetail(
val subdistrict: String, val subdistrict: String,
@field:SerializedName("recipient") @field:SerializedName("recipient")
val recipient: String, val recipient: String?,
@field:SerializedName("id") @field:SerializedName("id")
val id: Int, val id: Int,

View File

@ -63,6 +63,7 @@ import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreP
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetailResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
@ -528,4 +529,9 @@ interface ApiService {
suspend fun postResetPass( suspend fun postResetPass(
@Body request: ResetPassReq @Body request: ResetPassReq
): Response<ResetPassResponse> ): Response<ResetPassResponse>
@GET("profile/address/detail/{id}")
suspend fun getDetailAddress(
@Path("id") addressId: Int
): Response<AddressDetailResponse>
} }

View File

@ -20,12 +20,16 @@ import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityRespon
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse import com.alya.ecommerce_serang.data.api.response.customer.order.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.DetailPaymentItem import com.alya.ecommerce_serang.data.api.response.customer.product.DetailPaymentItem
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetailResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse 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
@ -294,6 +298,43 @@ class OrderRepository(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 getAddressDetail(addressId: Int): AddressDetailResponse? {
return try {
Log.d("Order Repository", "Fetching address detail for ID: $addressId")
val response = apiService.getDetailAddress(addressId)
Log.d("Order Repository", "Response code: ${response.code()}")
Log.d("Order Repository", "Response message: ${response.message()}")
if (response.isSuccessful) {
val body = response.body()
Log.d("Order Repository", "Address detail response body: $body")
body
} else {
Log.w("Order Repository", "Failed to get address detail. Error body: ${response.errorBody()?.string()}")
null
}
} catch (e: Exception) {
Log.e("Order Repository", "Error getting address detail", e)
null
}
}
suspend fun updateAddress(addressId: Int, params: Map<String, Any>): Response<UpdateAddressResponse> {
return apiService.updateAddress(addressId, params)
}
suspend fun fetchUserProfile(): Result<UserProfile?> { suspend fun fetchUserProfile(): Result<UserProfile?> {
return try { return try {
val response = apiService.getUserProfile() val response = apiService.getUserProfile()
@ -319,6 +360,7 @@ class OrderRepository(private val apiService: ApiService) {
} }
} }
suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> { suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> {
return try { return try {
Log.d("OrderRepository", "Uploading payment proof...") Log.d("OrderRepository", "Uploading payment proof...")

View File

@ -30,6 +30,8 @@ class CartActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private lateinit var storeAdapter: StoreAdapter private lateinit var storeAdapter: StoreAdapter
private var TAG = "Cart Activity"
private val viewModel: CartViewModel by viewModels { private val viewModel: CartViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager) val apiService = ApiConfig.getApiService(sessionManager)
@ -135,12 +137,44 @@ class CartActivity : AppCompatActivity() {
} }
private fun startCheckoutWithWholesaleInfo(checkoutItems: List<CartItemCheckoutInfo>) { private fun startCheckoutWithWholesaleInfo(checkoutItems: List<CartItemCheckoutInfo>) {
// Extract cart item IDs and wholesale status val wholesalePriceMap = viewModel.cartItemWholesalePrice.value ?: emptyMap()
val cartItemIds = checkoutItems.map { it.cartItem.cartItemId }
val wholesaleArray = checkoutItems.map { it.isWholesale }.toBooleanArray()
// Start checkout activity with the cart items and wholesale info val updatedItems = checkoutItems.map { info ->
CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray) val wholesalePrice = wholesalePriceMap[info.cartItem.cartItemId]
val updatedCartItem = if (info.isWholesale && wholesalePrice != null) {
// Replace the price with wholesale price
info.cartItem.copy(price = wholesalePrice.toInt())
} else {
info.cartItem
}
// Debug log
Log.d(
TAG,
"cartItemId: ${updatedCartItem.cartItemId}, " +
"isWholesale: ${info.isWholesale}, " +
"wholesalePrice: $wholesalePrice, " +
"finalPrice: ${updatedCartItem.price}"
)
info.copy(cartItem = updatedCartItem)
}
val cartItemIds = updatedItems.map { it.cartItem.cartItemId }
val wholesaleArray = updatedItems.map { it.isWholesale }.toBooleanArray()
// 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() {
@ -233,7 +267,5 @@ class CartActivity : AppCompatActivity() {
val format = NumberFormat.getCurrencyInstance(Locale("id", "ID")) val format = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
return format.format(amount).replace("Rp", "Rp ") return format.format(amount).replace("Rp", "Rp ")
} }
} }

View File

@ -96,18 +96,40 @@ 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()) {
// Create a map of cart item IDs to wholesale status if available // Build map of cartItemId -> isWholesale
val wholesaleMap = if (isWholesaleArray != null && isWholesaleArray.size == cartItemIds.size) { val isWholesaleMap = if (isWholesaleArray != null && isWholesaleArray.size == cartItemIds.size) {
cartItemIds.mapIndexed { index, id -> id to isWholesaleArray[index] }.toMap() cartItemIds.mapIndexed { index, id ->
id to isWholesaleArray[index]
}.toMap()
} else { } else {
emptyMap() emptyMap()
} }
viewModel.initializeFromCart(cartItemIds, wholesaleMap) // Build wholesalePriceMap - FIX: Map cartItemId to wholesale price
val wholesalePriceMap = if (wholesalePricesArray != null && wholesalePricesArray.size == cartItemIds.size) {
cartItemIds.mapIndexed { index, id ->
id to wholesalePricesArray[index]
}.toMap()
} else {
emptyMap()
}
viewModel.initializeFromCart(
cartItemIds,
isWholesaleMap,
wholesalePriceMap
)
Log.d("CheckoutActivity", "Cart IDs: $cartItemIds")
Log.d("CheckoutActivity", "IsWholesaleArray: ${isWholesaleArray?.joinToString()}")
Log.d("CheckoutActivity", "WholesalePricesArray: ${wholesalePricesArray?.joinToString()}")
Log.d("CheckoutActivity", "IsWholesaleMap: $isWholesaleMap")
Log.d("CheckoutActivity", "WholesalePriceMap: $wholesalePriceMap")
} else { } else {
Toast.makeText(this, "Error: No cart items specified", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Tidak ada item keranjang", Toast.LENGTH_SHORT).show()
finish() finish()
} }
} }
@ -416,6 +438,7 @@ class CheckoutActivity : AppCompatActivity() {
const val EXTRA_PRICE = "PRICE" const val EXTRA_PRICE = "PRICE"
const val EXTRA_ISWHOLESALE = "ISWHOLESALE" const val EXTRA_ISWHOLESALE = "ISWHOLESALE"
const val EXTRA_CART_ITEM_WHOLESALE = "EXTRA_CART_ITEM_WHOLESALE" const val EXTRA_CART_ITEM_WHOLESALE = "EXTRA_CART_ITEM_WHOLESALE"
const val EXTRA_CART_ITEM_WHOLESALE_PRICES = "EXTRA_CART_ITEM_WHOLESALE_PRICES"
// Helper methods for starting activity // Helper methods for starting activity
@ -449,13 +472,17 @@ class CheckoutActivity : AppCompatActivity() {
fun startForCart( fun startForCart(
context: Context, context: Context,
cartItemIds: List<Int>, cartItemIds: List<Int>,
isWholesaleArray: BooleanArray? = null isWholesaleArray: BooleanArray? = null,
wholesalePrices: IntArray? = null
) { ) {
val intent = Intent(context, CheckoutActivity::class.java).apply { val intent = Intent(context, CheckoutActivity::class.java).apply {
putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray()) putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray())
if (isWholesaleArray != null) { if (isWholesaleArray != null) {
putExtra(EXTRA_CART_ITEM_WHOLESALE, isWholesaleArray) putExtra(EXTRA_CART_ITEM_WHOLESALE, isWholesaleArray)
} }
if (wholesalePrices != null) {
putExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES, wholesalePrices)
}
} }
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -93,30 +93,46 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} }
// Initialize checkout from cart // Initialize checkout from cart
fun initializeFromCart(cartItemIds: List<Int>, isWholesaleMap: Map<Int, Boolean> = emptyMap()) { fun initializeFromCart(
cartItemIds: List<Int>,
isWholesaleMap: Map<Int, Boolean> = emptyMap(),
wholesalePriceMap: Map<Int, Int> = emptyMap()
) {
viewModelScope.launch { viewModelScope.launch {
_isLoading.value = true _isLoading.value = true
try { try {
// Get cart data
val cartResult = repository.getCart() val cartResult = repository.getCart()
if (cartResult is Result.Success) { if (cartResult is Result.Success) {
// Find matching cart items
val matchingItems = mutableListOf<CartItemsItem>() val matchingItems = mutableListOf<CartItemsItem>()
var storeData: DataItemCart? = null var storeData: DataItemCart? = null
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()) {
matchingItems.addAll(storeItems) // ✅ Apply wholesale prices - Replace item prices with wholesale prices
val updatedItems = storeItems.map { item ->
val wholesalePrice = wholesalePriceMap[item.cartItemId]
val isWholesale = isWholesaleMap[item.cartItemId] ?: false
// 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)
storeData = store storeData = store
break break
} }
} }
if (matchingItems.isNotEmpty() && storeData != null) { if (matchingItems.isNotEmpty() && storeData != null) {
// Create initial OrderRequest object
val orderRequest = OrderRequest( val orderRequest = OrderRequest(
addressId = 0, addressId = 0,
paymentMethodId = 0, paymentMethodId = 0,
@ -126,21 +142,26 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
isNego = false, isNego = false,
cartItemId = cartItemIds, cartItemId = cartItemIds,
shipEtd = "", shipEtd = "",
// Add a list tracking which items are wholesale isReseller = isWholesaleMap.any { it.value }
isReseller = isWholesaleMap.any { it.value } // Set true if any item is wholesale
) )
// Create checkout data
_checkoutData.value = CheckoutData( _checkoutData.value = CheckoutData(
orderRequest = orderRequest, orderRequest = orderRequest,
productName = matchingItems.first().productName, productName = matchingItems.first().productName,
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 // Store the wholesale map cartItemWholesaleMap = isWholesaleMap
) )
Log.d(TAG, "CheckoutData initialized with ${matchingItems.size} items")
matchingItems.forEachIndexed { index, item ->
val isWholesale = isWholesaleMap[item.cartItemId] ?: false
Log.d(TAG, "Item $index: ${item.productName}, Price: ${item.price}, IsWholesale: $isWholesale")
}
// Calculate totals with updated prices
calculateSubtotal() calculateSubtotal()
calculateTotal() calculateTotal()
} else { } else {
@ -151,6 +172,7 @@ 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
} }
@ -405,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

@ -10,6 +10,9 @@ import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.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.response.customer.profile.AddressDetail
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.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
@ -31,6 +34,18 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
private val _citiesState = MutableLiveData<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<Result<List<SubdistrictsItem>>>()
val subdistrictState: LiveData<Result<List<SubdistrictsItem>>> = _subdistrictState
private val _villagesState = MutableLiveData<Result<List<VillagesItem>>>()
val villagesState: LiveData<Result<List<VillagesItem>>> = _villagesState
private val _userAddress = MutableLiveData<AddressDetail>()
val userAddress: LiveData<AddressDetail> = _userAddress
private val _editAddress = MutableLiveData<Boolean>()
val editAddress: LiveData<Boolean> get() = _editAddress
// Stored in SavedStateHandle for configuration changes // Stored in SavedStateHandle for configuration changes
var selectedProvinceId: Int? var selectedProvinceId: Int?
get() = savedStateHandle.get<Int>("selectedProvinceId") get() = savedStateHandle.get<Int>("selectedProvinceId")
@ -40,6 +55,10 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
get() = savedStateHandle.get<String>("selectedCityId") get() = savedStateHandle.get<String>("selectedCityId")
set(value) { savedStateHandle["selectedCityId"] = value } set(value) { savedStateHandle["selectedCityId"] = value }
var selectedSubdistrict: String? = null
var selectedSubdistrictId: String? = null
var selectedVillages: String? = null
init { init {
// Load provinces on initialization // Load provinces on initialization
getProvinces() getProvinces()
@ -125,20 +144,133 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
} }
} }
fun getSubdistrict(cityId: String) {
_subdistrictState.value = Result.Loading
viewModelScope.launch {
try {
selectedSubdistrictId = cityId
val result = repository.getListSubdistrict(cityId)
result?.let {
_subdistrictState.postValue(Result.Success(it.subdistricts))
Log.d(TAG, "Subdistrict loaded for city $cityId: ${it.subdistricts.size}")
} ?: run {
_subdistrictState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "Subdistrict result was null for city $cityId")
}
} catch (e: Exception) {
_subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching subdistrict for city $cityId", e)
}
}
}
fun getVillages(subdistrictId: String) {
_villagesState.value = Result.Loading
viewModelScope.launch {
try {
selectedVillages = subdistrictId
val result = repository.getListVillages(subdistrictId)
result?.let {
_villagesState.postValue(Result.Success(it.villages))
Log.d(TAG, "Villages loaded for subdistrict $subdistrictId: ${it.villages.size}")
} ?: run {
_villagesState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "Village result was null for subdistrict $subdistrictId")
}
} catch (e: Exception) {
_villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching villages for subdistrict $subdistrictId", e)
}
}
}
fun setSelectedProvinceId(id: Int) { fun setSelectedProvinceId(id: Int) {
selectedProvinceId = id selectedProvinceId = id
} }
fun updateSelectedCityId(id: String) { fun detailAddress(addressId: Int){
selectedCityId = id viewModelScope.launch {
try {
val response = repository.getAddressDetail(addressId)
if (response != null){
_userAddress.value = response.address
} else {
Log.e(TAG, "Failed load address detail")
}
} catch (e:Exception){
Log.e(TAG, "Error fetching address detail: $e", e)
}
}
} }
fun loadUserProfile(){ fun updateAddress(oldAddress: AddressDetail, newAddress: AddressDetail) {
val params = buildUpdateBody(oldAddress, newAddress)
if (params.isEmpty()) {
Log.d(TAG, "No changes detected")
_editAddress.value = false
return
}
viewModelScope.launch { viewModelScope.launch {
when (val result = userRepo.fetchUserProfile()){ try {
is Result.Success -> _userProfile.postValue(result.data) val response = repository.updateAddress(oldAddress.id, params)
is Result.Error -> _errorMessageUser.postValue(result.exception.message ?: "Unknown Error") _editAddress.value = response.isSuccessful
is Result.Loading -> null } catch (e: Exception) {
Log.e(TAG, "Error: ${e.message}")
_editAddress.value = false
}
}
}
private fun buildUpdateBody(oldAddress: AddressDetail, newAddress: AddressDetail): Map<String, Any> {
val params = mutableMapOf<String, Any>()
fun addIfChanged(key: String, oldValue: Any?, newValue: Any?) {
if (newValue != null && newValue != oldValue) {
params[key] = newValue
}
}
addIfChanged("street", oldAddress.street, newAddress.street)
addIfChanged("province_id", oldAddress.provinceId, newAddress.provinceId)
addIfChanged("detail", oldAddress.detail, newAddress.detail)
addIfChanged("subdistrict", oldAddress.subdistrict, newAddress.subdistrict)
addIfChanged("city_id", oldAddress.cityId, newAddress.cityId)
addIfChanged("village_id", oldAddress.villageId, newAddress.villageId)
addIfChanged("postal_code", oldAddress.postalCode, newAddress.postalCode)
addIfChanged("phone", oldAddress.phone, newAddress.phone)
addIfChanged("recipient", oldAddress.recipient, newAddress.recipient)
addIfChanged("latitude", oldAddress.latitude, newAddress.latitude)
addIfChanged("longitude", oldAddress.longitude, newAddress.longitude)
addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation)
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
}
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
}
} }
} }
} }

View File

@ -74,13 +74,17 @@ class AddressActivity : AppCompatActivity() {
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
adapter = AddressAdapter { address -> adapter = AddressAdapter(
// Select the address in the ViewModel onAddressClick = { address ->
viewModel.selectAddress(address.id) viewModel.selectAddress(address.id)
returnResultAndFinish(address.id)
// Return immediately with the selected address },
returnResultAndFinish(address.id) onEditClick = { address ->
} val intent = Intent(this, EditAddressActivity::class.java)
intent.putExtra(EditAddressActivity.EXTRA_ADDRESS_ID, address.id)
startActivity(intent)
}
)
binding.rvSellerOrder.apply { binding.rvSellerOrder.apply {
layoutManager = LinearLayoutManager(this@AddressActivity) layoutManager = LinearLayoutManager(this@AddressActivity)

View File

@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order.address
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -13,7 +14,8 @@ import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesIte
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
class AddressAdapter( class AddressAdapter(
private val onAddressClick: (AddressesItem) -> Unit private val onAddressClick: (AddressesItem) -> Unit,
private val onEditClick: (AddressesItem) -> Unit
) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) { ) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) {
private var selectedAddressId: Int? = null private var selectedAddressId: Int? = null
@ -47,18 +49,21 @@ class AddressAdapter(
// Pass the whole address object to provide more context // Pass the whole address object to provide more context
onAddressClick(address) onAddressClick(address)
} }
holder.editButton.setOnClickListener {
onEditClick(address)
}
} }
class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvName: TextView = itemView.findViewById(R.id.tv_name_address) private val tvName: TextView = itemView.findViewById(R.id.tv_name_address)
private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address) private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address)
val editButton: ImageView = itemView.findViewById(R.id.iv_edit)
private val card: MaterialCardView = itemView as MaterialCardView private val card: MaterialCardView = itemView as MaterialCardView
fun bind(address: AddressesItem, isSelected: Boolean) { fun bind(address: AddressesItem, isSelected: Boolean) {
tvName.text = address.recipient tvName.text = address.recipient
tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}" tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}"
// Make selection more visible
card.strokeWidth = if (isSelected) 3 else 0 card.strokeWidth = if (isSelected) 3 else 0
card.strokeColor = if (isSelected) card.strokeColor = if (isSelected)
ContextCompat.getColor(itemView.context, R.color.blue_400) ContextCompat.getColor(itemView.context, R.color.blue_400)

View File

@ -1,21 +1,458 @@
package com.alya.ecommerce_serang.ui.order.address package com.alya.ecommerce_serang.ui.order.address
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetail
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityEditAddressBinding
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
class EditAddressActivity : AppCompatActivity() { class EditAddressActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) private lateinit var binding: ActivityEditAddressBinding
enableEdgeToEdge() private lateinit var apiService: ApiService
setContentView(R.layout.activity_edit_address) private lateinit var sessionManager: SessionManager
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) private var latitude: Double? = null
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) private var longitude: Double? = null
insets private var addressId: Int = -1
private var currentAddress: AddressDetail? = null
private val provinceAdapter by lazy { ProvinceAdapter(this) }
private val cityAdapter by lazy { CityAdapter(this) }
private val subdistrictAdapter by lazy { SubdsitrictAdapter(this)}
private val villageAdapter by lazy { VillagesAdapter(this)}
private var provincesList = mutableListOf<ProvincesItem>()
private var citiesList = mutableListOf<CitiesItem>()
private var subdistrictsList = mutableListOf<SubdistrictsItem>()
private var villagesList = mutableListOf<VillagesItem>()
private val viewModel: AddAddressViewModel by viewModels {
SavedStateViewModelFactory(this) { savedStateHandle ->
val apiService = ApiConfig.getApiService(sessionManager)
val orderRepository = OrderRepository(apiService)
val userRepository = UserRepository(apiService)
AddAddressViewModel(orderRepository, userRepository, savedStateHandle)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEditAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge()
// Apply insets to your root layout
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
windowInsets
}
addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, -1)
if (addressId == -1) {
Toast.makeText(this, "Gagal mendapatkan alamat pengguna", Toast.LENGTH_SHORT).show()
finish()
return
}
setupToolbar()
setupAdapters()
setupObservers()
setupListeners()
// Load address detail first
Log.d(TAG, "Loading address with ID: $addressId")
viewModel.detailAddress(addressId)
}
private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
private fun setupAdapters() {
// Set custom adapters to AutoCompleteTextViews
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter)
binding.autoCompleteDesa.setAdapter(villageAdapter)
}
private fun setupObservers() {
// Observe address detail
viewModel.userAddress.observe(this) { address ->
currentAddress = address
Log.d(TAG, "Address loaded: $address")
populateAddressData(address)
}
// Observe provinces
viewModel.provincesState.observe(this) { viewState ->
when (viewState) {
is ViewState.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
is ViewState.Success -> {
binding.progressBar.visibility = View.GONE
provincesList.clear()
provincesList.addAll(viewState.data)
provinceAdapter.updateData(viewState.data)
// Set selected province if address is loaded
currentAddress?.let { address ->
setSelectedProvince(address.provinceId.toInt())
}
}
is ViewState.Error -> {
binding.progressBar.visibility = View.GONE
Log.e(TAG, "Failed to load province ${viewState.message}")
}
}
}
// Observe cities
viewModel.citiesState.observe(this) { viewState ->
when (viewState) {
is ViewState.Loading -> {
binding.cityProgressBar.visibility = View.VISIBLE
}
is ViewState.Success -> {
binding.cityProgressBar.visibility = View.GONE
citiesList.clear()
citiesList.addAll(viewState.data)
cityAdapter.updateData(viewState.data)
// Set selected city if address is loaded
currentAddress?.let { address ->
setSelectedCity(address.cityId)
}
}
is ViewState.Error -> {
binding.cityProgressBar.visibility = View.GONE
Log.e(TAG, "Failed to load cities ${viewState.message}")
}
}
}
// Observe subdistricts
viewModel.subdistrictState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
// You can add loading indicator for subdistrict if needed
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
subdistrictsList.clear()
subdistrictsList.addAll(result.data)
subdistrictAdapter.updateData(result.data)
// Set selected subdistrict if address is loaded
currentAddress?.let { address ->
setSelectedSubdistrict(address.subdistrict)
}
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e(TAG, "Failed to load subdistricy")
}
}
}
// Observe villages
viewModel.villagesState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
// You can add loading indicator for village if needed
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
villagesList.clear()
villagesList.addAll(result.data)
villageAdapter.updateData(result.data)
// Set selected village if address is loaded
currentAddress?.let { address ->
setSelectedVillage(address.villageId)
}
}
is Result.Error -> {
Log.e(TAG, "Failed to load villages")
}
}
}
// Observe update result
viewModel.editAddress.observe(this) { isSuccess ->
binding.submitProgressBar.visibility = View.GONE
binding.buttonSimpan.isEnabled = true
if (isSuccess) {
Log.d(TAG, "Address updated successfully")
finish()
} else {
Log.d(TAG, "Failed to update address")
}
}
}
private fun setupListeners() {
// Province selection
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
val provinceId = provinceAdapter.getProvinceId(position)
provinceId?.let { id ->
viewModel.getCities(id)
// Clear dependent dropdowns
clearCitySelection()
clearSubdistrictSelection()
clearVillageSelection()
}
}
// City selection
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
val cityId = cityAdapter.getCityId(position)
cityId?.let { id ->
viewModel.getSubdistrict(id)
// Clear dependent dropdowns
clearSubdistrictSelection()
clearVillageSelection()
}
}
// Subdistrict selection
binding.autoCompleteKecamatan.setOnItemClickListener { _, _, position, _ ->
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
subdistrictId?.let { id ->
viewModel.getVillages(id)
// Clear dependent dropdowns
clearVillageSelection()
}
}
// Village selection - auto-populate postal code
binding.autoCompleteDesa.setOnItemClickListener { _, _, position, _ ->
// val postalCode = villageAdapter.getPostalCode(position)
// postalCode?.let {
// binding.etKodePos.setText(it)
// }
}
// Save button
binding.buttonSimpan.setOnClickListener {
saveAddress()
}
}
private fun populateAddressData(address: AddressDetail) {
binding.etNamaPenerima.setText(address.recipient ?: "")
binding.etNomorHp.setText(address.phone ?: "")
binding.etDetailAlamat.setText(address.detail ?: "")
binding.etKodePos.setText(address.postalCode ?: "")
// Province will be set when provinces are loaded
// City, subdistrict, village will be set in their respective observers
}
private fun setSelectedProvince(provinceId: Int?) {
provinceId?.let { id ->
val position = provincesList.indexOfFirst { it.provinceId?.toIntOrNull() == id }
if (position >= 0) {
val provinceName = provincesList[position].province
binding.autoCompleteProvinsi.setText(provinceName, false)
viewModel.getCities(id)
}
}
}
private fun setSelectedCity(cityId: String?) {
cityId?.let { id ->
val position = citiesList.indexOfFirst { it.cityId?.toString() == id }
if (position >= 0) {
val cityName = citiesList[position].cityName
binding.autoCompleteKabupaten.setText(cityName, false)
viewModel.getSubdistrict(id)
}
}
}
private fun setSelectedSubdistrict(subdistrictId: String?) {
subdistrictId?.let { id ->
val position = subdistrictsList.indexOfFirst { it.subdistrictId?.toString() == id }
if (position >= 0) {
val subdistrictName = subdistrictsList[position].subdistrictName
binding.autoCompleteKecamatan.setText(subdistrictName, false)
viewModel.getVillages(id)
}
}
}
private fun setSelectedVillage(villageId: String?) {
villageId?.let { id ->
val position = villagesList.indexOfFirst { it.villageId?.toString() == id }
if (position >= 0) {
val villageName = villagesList[position].villageName
binding.autoCompleteDesa.setText(villageName, false)
}
}
}
//
// private fun populatePostalCodeFromVillage(villageId: String?) {
// villageId?.let { id ->
// val village = villagesList.find { it.villageId?.toString() == id }
// village?.postalCode?.let { postalCode ->
// if (binding.etKodePos.text.isNullOrEmpty()) {
// binding.etKodePos.setText(postalCode)
// }
// }
// }
// }
private fun clearCitySelection() {
binding.autoCompleteKabupaten.setText("", false)
citiesList.clear()
cityAdapter.updateData(emptyList())
}
private fun clearSubdistrictSelection() {
binding.autoCompleteKecamatan.setText("", false)
subdistrictsList.clear()
subdistrictAdapter.updateData(emptyList())
}
private fun clearVillageSelection() {
binding.autoCompleteDesa.setText("", false)
binding.etKodePos.setText("") // Clear postal code when village is cleared
villagesList.clear()
villageAdapter.updateData(emptyList())
}
private fun saveAddress() {
currentAddress?.let { oldAddress ->
val newAddress = createNewAddressFromInputs(oldAddress)
binding.submitProgressBar.visibility = View.VISIBLE
binding.buttonSimpan.isEnabled = false
viewModel.updateAddress(oldAddress, newAddress)
} ?: run {
Log.d(TAG, "Address not loaded")
Toast.makeText(this, "Gagal mendapatkan alamat", Toast.LENGTH_SHORT).show()
}
}
private fun createNewAddressFromInputs(oldAddress: AddressDetail): AddressDetail {
val selectedProvinceId = getSelectedProvinceId()
val selectedCityId = getSelectedCityId()
val selectedSubdistrictName = getSelectedSubdistrictName()
val selectedVillageId = getSelectedVillageId()
val selectedSubdistrictId = getSelectedSubdistrictId()
val selectedVillageName = getSelectedVillageName()
val selectedProvinceName = getSelectedProvinceName()
Log.d(TAG, "Old subdistrict: ${oldAddress.subdistrict}")
Log.d(TAG, "Selected subdistrictId: $selectedSubdistrictName")
val newAddress = oldAddress.copy(
recipient = binding.etNamaPenerima.text.toString().trim(),
phone = binding.etNomorHp.text.toString().trim(),
detail = binding.etDetailAlamat.text.toString().trim(),
postalCode = binding.etKodePos.text.toString().trim(),
provinceId = selectedProvinceId?.toString() ?: oldAddress.provinceId,
cityId = selectedCityId ?: oldAddress.cityId,
subdistrict = selectedSubdistrictName ?: oldAddress.subdistrict,
villageId = selectedVillageId,
subdistrictId = selectedSubdistrictId ?: oldAddress.subdistrictId,
villageName = selectedVillageName ?: oldAddress.villageName,
provinceName = selectedProvinceName ?: oldAddress.provinceName
)
// 🔎 Debug logs
Log.d(TAG, "New subdistrict: ${newAddress.subdistrict}")
Log.d(TAG, "New village name: ${newAddress.villageName}")
return newAddress
}
private fun getSelectedProvinceId(): Int? {
val selectedText = binding.autoCompleteProvinsi.text.toString()
val position = provincesList.indexOfFirst { it.province == selectedText }
return if (position >= 0) provinceAdapter.getProvinceId(position) else null
}
private fun getSelectedProvinceName(): String? {
val selectedText = binding.autoCompleteProvinsi.text.toString()
val position = provincesList.indexOfFirst { it.province == selectedText }
return if (position >= 0) provinceAdapter.getProvinceName(position) else null
}
private fun getSelectedCityId(): String? {
val selectedText = binding.autoCompleteKabupaten.text.toString()
val position = citiesList.indexOfFirst { it.cityName == selectedText }
return if (position >= 0) cityAdapter.getCityId(position) else null
}
private fun getSelectedCityName(): String? {
val selectedText = binding.autoCompleteKabupaten.text.toString()
val position = citiesList.indexOfFirst { it.cityName == selectedText }
return if (position >= 0) cityAdapter.getCityName(position) else null
}
private fun getSelectedSubdistrictName(): String? {
val selectedText = binding.autoCompleteKecamatan.text.toString()
val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText }
return if (position >= 0) subdistrictAdapter.getSubdistrictName(position) else null
}
private fun getSelectedSubdistrictId(): String? {
val selectedText = binding.autoCompleteKecamatan.text.toString()
val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText }
return if (position >= 0) subdistrictAdapter.getSubdistrictId(position) else null
}
private fun getSelectedVillageId(): String? {
val selectedText = binding.autoCompleteDesa.text.toString()
val position = villagesList.indexOfFirst { it.villageName == selectedText }
return if (position >= 0) villageAdapter.getVillageId(position) else null
}
private fun getSelectedVillageName(): String? {
val selectedText = binding.autoCompleteDesa.text.toString()
val position = villagesList.indexOfFirst { it.villageName == selectedText }
return if (position >= 0) villageAdapter.getVillageName(position) else null
}
companion object {
const val EXTRA_ADDRESS_ID = "extra_address_id"
private const val TAG = "EditAddressActivity"
}
} }

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

@ -5,6 +5,257 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.order.address.EditAddressActivity"> android:background="@android:color/white"
tools:context=".ui.order.address.AddAddressActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_back_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:title="Ubah Alamat" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/buttonSimpan"
app:layout_constraintTop_toBottomOf="@id/divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama Penerima"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etNamaPenerima"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi nama penerima"
android:inputType="textPersonName"
android:padding="12dp"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Nomor Hp"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etNomorHp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi nomor handphone aktif"
android:inputType="phone"
android:padding="12dp"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Detail Alamat"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etDetailAlamat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:gravity="top"
android:hint="Isi detail alamat (nomor rumah, lantai, dll)"
android:inputType="textMultiLine"
android:lines="3"
android:padding="12dp"
android:textSize="14sp" />
<!-- Provinsi -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Provinsi"
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 Provinsi"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteProvinsi"
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/progressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<!-- Kabupaten / Kota -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kabupaten / Kota"
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 Kota / Kabupaten"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteKabupaten"
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/cityProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<!-- Kecamatan -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kecamatan"
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 Kecamatan"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteKecamatan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<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"
android:hint="Pilih 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>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kode Pos"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etKodePos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi jawaban Anda di sini"
android:inputType="number"
android:padding="12dp"
android:textSize="14sp" />
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/buttonSimpan"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_margin="16dp"
android:background="@drawable/button_address_background"
android:text="Simpan"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" />
<ProgressBar
android:id="@+id/submitProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/buttonSimpan"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -36,8 +36,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/baseline_edit_24" android:src="@drawable/baseline_edit_24"
android:focusable="true"
android:clickable="true"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<TextView <TextView

BIN
pasarKlik.apk Normal file

Binary file not shown.

BIN
unduh/PasarKlik_v2.apk Normal file

Binary file not shown.