mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-12-15 15:41:02 +00:00
fix register store and product
This commit is contained in:
@ -0,0 +1,21 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.io.File
|
||||
|
||||
data class PaymentUpdate(
|
||||
@field:SerializedName("id")
|
||||
val id: Int? = null,
|
||||
|
||||
@field:SerializedName("bank_name")
|
||||
val bankName: String,
|
||||
|
||||
@field:SerializedName("bank_num")
|
||||
val bankNum: String,
|
||||
|
||||
@field:SerializedName("account_name")
|
||||
val accountName: String,
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: File? = null
|
||||
)
|
||||
@ -1,7 +1,6 @@
|
||||
package com.alya.ecommerce_serang.data.api.retrofit
|
||||
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.AddPaymentInfoResponse
|
||||
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
|
||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||
@ -17,7 +16,6 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
|
||||
@ -29,7 +27,6 @@ import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.ListNotifResponse
|
||||
@ -83,12 +80,10 @@ import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductRe
|
||||
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.review.ProductReviewResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
@ -99,7 +94,6 @@ import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PartMap
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface ApiService {
|
||||
@POST("registeruser")
|
||||
@ -112,9 +106,6 @@ interface ApiService {
|
||||
@Body verifRegisReq: VerifRegisReq
|
||||
):VerifRegisterResponse
|
||||
|
||||
@GET("checkstore")
|
||||
suspend fun checkStore (): Response<CheckStoreResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("registerstore")
|
||||
suspend fun registerStore(
|
||||
@ -204,11 +195,6 @@ interface ApiService {
|
||||
@Path("id") orderId: Int
|
||||
): Response<OrderDetailResponse>
|
||||
|
||||
@POST("order/addevidence")
|
||||
suspend fun addEvidence(
|
||||
@Body request : AddEvidenceRequest,
|
||||
): Response<AddEvidenceResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("order/addevidence")
|
||||
suspend fun addEvidenceMultipart(
|
||||
@ -256,15 +242,9 @@ interface ApiService {
|
||||
@GET("mystore")
|
||||
suspend fun getMyStoreData(): Response<com.alya.ecommerce_serang.data.api.response.store.StoreResponse>
|
||||
|
||||
@GET("mystore")
|
||||
suspend fun getStoreAddress(): Response<StoreAddressResponse>
|
||||
|
||||
@GET("mystore/product") // Replace with actual endpoint
|
||||
suspend fun getStoreProduct(): Response<ViewStoreProductsResponse>
|
||||
|
||||
@GET("category")
|
||||
fun getCategories(): Call<CategoryResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("store/createproduct")
|
||||
suspend fun addProduct(
|
||||
@ -374,9 +354,6 @@ interface ApiService {
|
||||
@GET("store/topup")
|
||||
suspend fun getTopUpHistory(): Response<TopUpResponse>
|
||||
|
||||
@GET("store/topup")
|
||||
suspend fun getFilteredTopUpHistory(@Query("date") date: String): Response<TopUpResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("store/createtopup")
|
||||
suspend fun addBalanceTopUp(
|
||||
@ -388,11 +365,6 @@ interface ApiService {
|
||||
@Part("bank_num") bankNum: RequestBody
|
||||
): Response<BalanceTopUpResponse>
|
||||
|
||||
@PUT("store/payment/update")
|
||||
suspend fun paymentConfirmation(
|
||||
@Body confirmPaymentReq : PaymentConfirmRequest
|
||||
): Response<PaymentConfirmationResponse>
|
||||
|
||||
@Multipart
|
||||
@PUT("mystore/edit")
|
||||
suspend fun updateStoreProfileMultipart(
|
||||
@ -404,13 +376,26 @@ interface ApiService {
|
||||
): Response<StoreDataResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("mystore/payment/add")
|
||||
suspend fun addPaymentInfo(
|
||||
@Part("bank_name") bankName: RequestBody,
|
||||
@Part("bank_num") bankNum: RequestBody,
|
||||
@Part("account_name") accountName: RequestBody,
|
||||
@Part qris: MultipartBody.Part?
|
||||
): Response<GenericResponse>
|
||||
@PUT("mystore/edit")
|
||||
suspend fun updateStoreApprovalMultipart(
|
||||
@Part("store_name") storeName: RequestBody,
|
||||
@Part("store_description") storeDescription: RequestBody,
|
||||
@Part("store_type_id") storeTypeId: RequestBody,
|
||||
@Part("latitude") storeLatitude: RequestBody,
|
||||
@Part("longitude") storeLongitude: RequestBody,
|
||||
@Part("province_id") storeProvince: RequestBody,
|
||||
@Part("city_id") storeCity: RequestBody,
|
||||
@Part("subdistrict") storeSubdistrict: RequestBody,
|
||||
@Part("village_id") storeVillage: RequestBody,
|
||||
@Part("street") storeStreet: RequestBody,
|
||||
@Part("postal_code") storePostalCode: RequestBody,
|
||||
@Part("detail") storeAddressDetail: RequestBody,
|
||||
@Part("user_phone") storeUserPhone: RequestBody,
|
||||
@Part storeimg: MultipartBody.Part?,
|
||||
@Part ktp: MultipartBody.Part?,
|
||||
@Part npwp: MultipartBody.Part?,
|
||||
@Part nib: MultipartBody.Part?
|
||||
): Response<StoreDataResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("mystore/payment/add")
|
||||
@ -421,6 +406,16 @@ interface ApiService {
|
||||
@Part qris: MultipartBody.Part?
|
||||
): Response<AddPaymentInfoResponse>
|
||||
|
||||
@Multipart
|
||||
@PUT("mystore/payment/edit")
|
||||
suspend fun updatePaymentInfo(
|
||||
@Part("payment_info_id") paymentInfoId: RequestBody,
|
||||
@Part("account_name") accountName: RequestBody,
|
||||
@Part("bank_name") bankName: RequestBody,
|
||||
@Part("bank_num") bankNum: RequestBody,
|
||||
@Part qris: MultipartBody.Part? = null
|
||||
): Response<GenericResponse>
|
||||
|
||||
@DELETE("mystore/payment/delete/{id}")
|
||||
suspend fun deletePaymentInfo(
|
||||
@Path("id") paymentMethodId: Int
|
||||
@ -464,15 +459,6 @@ interface ApiService {
|
||||
@GET("search")
|
||||
suspend fun getSearchHistory(): Response<SearchHistoryResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("sendchat")
|
||||
suspend fun sendChatLine(
|
||||
@Part("store_id") storeId: RequestBody,
|
||||
@Part("message") message: RequestBody,
|
||||
@Part("product_id") productId: RequestBody?,
|
||||
@Part chatimg: MultipartBody.Part?
|
||||
): Response<SendChatResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("store/sendchat")
|
||||
suspend fun sendChatMessageStore(
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
package com.alya.ecommerce_serang.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.Store
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class MyStoreRepository(private val apiService: ApiService) {
|
||||
@ -139,6 +145,186 @@ class MyStoreRepository(private val apiService: ApiService) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateStoreApproval(
|
||||
storeName: RequestBody,
|
||||
description: RequestBody,
|
||||
storeType: RequestBody,
|
||||
latitude: RequestBody,
|
||||
longitude: RequestBody,
|
||||
storeProvince: RequestBody,
|
||||
storeCity: RequestBody,
|
||||
storeSubdistrict: RequestBody,
|
||||
storeVillage: RequestBody,
|
||||
storeStreet: RequestBody,
|
||||
storePostalCode: RequestBody,
|
||||
storeAddressDetail: RequestBody,
|
||||
userPhone: RequestBody,
|
||||
paymentsToUpdate: List<PaymentUpdate> = emptyList(),
|
||||
paymentIdToDelete: List<Int> = emptyList(),
|
||||
storeCourier: List<String>? = null,
|
||||
storeImage: MultipartBody.Part?,
|
||||
ktpImage: MultipartBody.Part?,
|
||||
npwpDocument: MultipartBody.Part?,
|
||||
nibDocument: MultipartBody.Part?
|
||||
): Response<StoreDataResponse>? {
|
||||
return try {
|
||||
Log.d(TAG, "Updating store profile & address for approval...")
|
||||
|
||||
val profileResp = apiService.updateStoreApprovalMultipart(
|
||||
storeName = storeName,
|
||||
storeDescription = description,
|
||||
storeTypeId = storeType,
|
||||
storeLatitude = latitude,
|
||||
storeLongitude = longitude,
|
||||
storeProvince = storeProvince,
|
||||
storeCity = storeCity,
|
||||
storeSubdistrict = storeSubdistrict,
|
||||
storeVillage = storeVillage,
|
||||
storeStreet = storeStreet,
|
||||
storePostalCode = storePostalCode,
|
||||
storeAddressDetail = storeAddressDetail,
|
||||
storeUserPhone = userPhone,
|
||||
storeimg = storeImage,
|
||||
ktp = ktpImage,
|
||||
npwp = npwpDocument,
|
||||
nib = nibDocument
|
||||
)
|
||||
|
||||
if (!profileResp.isSuccessful) {
|
||||
Log.e(TAG, "Profile update failed: ${profileResp.code()} ${profileResp.errorBody()?.string()}")
|
||||
return profileResp // short-circuit; let caller inspect the failure
|
||||
}
|
||||
|
||||
// 2) Payments: delete, then upsert (safer if you’re changing accounts)
|
||||
if (paymentIdToDelete.isNotEmpty() || paymentsToUpdate.isNotEmpty()) {
|
||||
Log.d(TAG, "Synchronizing payments: delete=${paymentIdToDelete.size}, upsert=${paymentsToUpdate.size}")
|
||||
}
|
||||
|
||||
// 2a) Delete payments
|
||||
paymentIdToDelete.forEach { id ->
|
||||
runCatching {
|
||||
apiService.deletePaymentInfo(id)
|
||||
}.onSuccess {
|
||||
if (!it.isSuccessful) {
|
||||
Log.e(TAG, "Delete payment $id failed: ${it.code()} ${it.errorBody()?.string()}")
|
||||
} else {
|
||||
Log.d(TAG, "Deleted payment $id")
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "Delete payment $id exception", e)
|
||||
}
|
||||
}
|
||||
|
||||
// 2b) Upsert payments (add if id==null, else update)
|
||||
paymentsToUpdate.forEach { item ->
|
||||
runCatching {
|
||||
// --- CHANGE HERE if your PaymentUpdate field names differ ---
|
||||
val id = item.id // Int? (null => add)
|
||||
val bankName = item.bankName // String
|
||||
val bankNum = item.bankNum // String
|
||||
val accountName = item.accountName // String
|
||||
val qrisImage = item.qrisImage // File? (Optional)
|
||||
// -----------------------------------------------------------
|
||||
|
||||
if (id == null) {
|
||||
// ADD
|
||||
val resp = apiService.addPaymentInfoDirect(
|
||||
bankName = bankName.toPlain(),
|
||||
bankNum = bankNum.toPlain(),
|
||||
accountName = accountName.toPlain(),
|
||||
qris = createQrisPartOrNull(qrisImage)
|
||||
)
|
||||
if (!resp.isSuccessful) {
|
||||
Log.e(TAG, "Add payment failed: ${resp.code()} ${resp.errorBody()?.string()}")
|
||||
} else {
|
||||
Log.d(TAG, "Added payment: $bankName/$bankNum")
|
||||
}
|
||||
} else {
|
||||
// UPDATE
|
||||
val resp = apiService.updatePaymentInfo(
|
||||
paymentInfoId = id.toString().toPlain(),
|
||||
accountName = accountName.toPlain(),
|
||||
bankName = bankName.toPlain(),
|
||||
bankNum = bankNum.toPlain(),
|
||||
qris = createQrisPartOrNull(qrisImage)
|
||||
)
|
||||
if (!resp.isSuccessful) {
|
||||
Log.e(TAG, "Update payment $id failed: ${resp.code()} ${resp.errorBody()?.string()}")
|
||||
} else {
|
||||
Log.d(TAG, "Updated payment $id: $bankName/$bankNum")
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "Upsert payment exception", e)
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Shipping: sync to desiredCouriers (if provided)
|
||||
storeCourier?.let { desired ->
|
||||
try {
|
||||
val current = apiService.getStoreData().let { resp ->
|
||||
if (resp.isSuccessful) {
|
||||
resp.body()?.shipping?.mapNotNull { it.courier } ?: emptyList()
|
||||
} else {
|
||||
Log.e(TAG, "Failed to read current shipping: ${resp.code()} ${resp.errorBody()?.string()}")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
val desiredSet = desired.toSet()
|
||||
val currentSet = current.toSet()
|
||||
|
||||
val toAdd = (desiredSet - currentSet).toList()
|
||||
val toDel = (currentSet - desiredSet).toList()
|
||||
|
||||
if (toAdd.isNotEmpty()) {
|
||||
val addResp = apiService.addShippingService(ShippingServiceRequest(couriers = toAdd))
|
||||
if (!addResp.isSuccessful) {
|
||||
Log.e(TAG, "Add couriers failed: ${addResp.code()} ${addResp.errorBody()?.string()}")
|
||||
} else {
|
||||
Log.d(TAG, "Added couriers: $toAdd")
|
||||
}
|
||||
}
|
||||
|
||||
if (toDel.isNotEmpty()) {
|
||||
val delResp = apiService.deleteShippingService(ShippingServiceRequest(couriers = toDel))
|
||||
if (!delResp.isSuccessful) {
|
||||
Log.e(TAG, "Delete couriers failed: ${delResp.code()} ${delResp.errorBody()?.string()}")
|
||||
} else {
|
||||
Log.d(TAG, "Deleted couriers: $toDel")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Sync shipping exception", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the profile response (already successful here)
|
||||
profileResp
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error updating store approval flow", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toPlain(): RequestBody =
|
||||
this.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
|
||||
private fun createQrisPartOrNull(file: File?): MultipartBody.Part? =
|
||||
file?.let {
|
||||
val mime = when (it.extension.lowercase()) {
|
||||
"jpg", "jpeg" -> "image/jpeg"
|
||||
"png" -> "image/png"
|
||||
else -> "application/octet-stream"
|
||||
}.toMediaTypeOrNull()
|
||||
|
||||
MultipartBody.Part.createFormData(
|
||||
"qris",
|
||||
it.name,
|
||||
it.asRequestBody(mime)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var TAG = "MyStoreRepository"
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.File
|
||||
import androidx.core.net.toUri
|
||||
import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate
|
||||
|
||||
class RegisterStoreActivity : AppCompatActivity() {
|
||||
|
||||
@ -58,6 +59,21 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
private lateinit var subdistrictAdapter: SubdsitrictAdapter
|
||||
private lateinit var bankAdapter: BankAdapter
|
||||
|
||||
// pending values (filled from myStoreProfile once)
|
||||
private var wantedProvinceId: Int? = null
|
||||
private var wantedCityId: String? = null
|
||||
private var wantedSubdistrictId: String? = null
|
||||
private var wantedBankName: String? = null
|
||||
|
||||
// one-shot guards so we don't re-apply repeatedly
|
||||
private var provinceApplied = false
|
||||
private var cityApplied = false
|
||||
private var subdistrictApplied = false
|
||||
private var bankApplied = false
|
||||
|
||||
// avoid clearing/overriding while restoring
|
||||
private var isRestoringSelections = false
|
||||
|
||||
// Request codes for file picking
|
||||
private val PICK_STORE_IMAGE_REQUEST = 1001
|
||||
private val PICK_KTP_REQUEST = 1002
|
||||
@ -123,6 +139,10 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
setupSpinners() // Location spinners
|
||||
Log.d(TAG, "onCreate: Spinners setup completed")
|
||||
|
||||
binding.checkboxApprove.setOnCheckedChangeListener { _, _ ->
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
// Setup observers
|
||||
setupStoreTypesObserver() // Store type observer
|
||||
setupObservers()
|
||||
@ -209,12 +229,27 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
// Prefill spinner for store types
|
||||
preselectStoreType(store.storeTypeId)
|
||||
|
||||
// Prefill province, city, and subdistrict
|
||||
preselectProvinceCitySubdistrict(
|
||||
provinceId = store.provinceId,
|
||||
cityId = store.cityId,
|
||||
subdistrictId = store.subdistrict
|
||||
)
|
||||
// Cache what we want to select later (after data arrives)
|
||||
wantedProvinceId = store.provinceId
|
||||
wantedCityId = store.cityId
|
||||
wantedSubdistrictId = store.subdistrict
|
||||
wantedBankName = storeResponse.payment.firstOrNull()?.bankName
|
||||
|
||||
// Cache what we want to select later (after data arrives)
|
||||
wantedProvinceId = store.provinceId
|
||||
wantedCityId = store.cityId
|
||||
wantedSubdistrictId = store.subdistrict
|
||||
wantedBankName = storeResponse.payment.firstOrNull()?.bankName
|
||||
|
||||
// Mark restoring flow on
|
||||
isRestoringSelections = true
|
||||
|
||||
// Try to apply immediately (if adapters already have data), otherwise
|
||||
// observers below will apply when data is ready.
|
||||
tryApplyProvince()
|
||||
tryApplyCity()
|
||||
tryApplySubdistrict()
|
||||
tryApplyBank()
|
||||
|
||||
validateRequiredFields()
|
||||
}
|
||||
@ -229,64 +264,78 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
else Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
private fun preselectStoreType(storeTypeId: Int) {
|
||||
// The adapter is created in setupStoreTypeSpinner(...)
|
||||
val adapter = binding.spinnerStoreType.adapter
|
||||
if (adapter != null) {
|
||||
val count = adapter.count
|
||||
for (i in 0 until count) {
|
||||
val item = adapter.getItem(i) as? StoreTypesItem
|
||||
if (item?.id == storeTypeId) {
|
||||
binding.spinnerStoreType.setSelection(i, false)
|
||||
break
|
||||
}
|
||||
val adapter = binding.spinnerStoreType.adapter ?: return
|
||||
for (i in 0 until adapter.count) {
|
||||
val item = adapter.getItem(i) as? StoreTypesItem
|
||||
if (item?.id == storeTypeId) {
|
||||
binding.spinnerStoreType.setSelection(i, false)
|
||||
viewModel.storeTypeId.value = storeTypeId
|
||||
validateRequiredFields()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun preselectProvinceCitySubdistrict(
|
||||
provinceId: Int,
|
||||
cityId: String,
|
||||
subdistrictId: String
|
||||
) {
|
||||
// Province first (this will trigger cities fetch)
|
||||
val provCount = provinceAdapter.count
|
||||
for (i in 0 until provCount) {
|
||||
if (provinceAdapter.getProvinceId(i) == provinceId) {
|
||||
private fun tryApplyProvince() {
|
||||
if (provinceApplied) return
|
||||
val target = wantedProvinceId ?: return
|
||||
val count = provinceAdapter.count
|
||||
for (i in 0 until count) {
|
||||
if (provinceAdapter.getProvinceId(i) == target) {
|
||||
binding.spinnerProvince.setSelection(i, false)
|
||||
break
|
||||
provinceApplied = true
|
||||
maybeFinishRestoring()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When cities arrive, select the city, then load subdistricts
|
||||
viewModel.citiesState.observe(this) { state ->
|
||||
if (state is Result.Success) {
|
||||
val cityCount = cityAdapter.count
|
||||
for (i in 0 until cityCount) {
|
||||
if (cityAdapter.getCityId(i) == cityId) {
|
||||
binding.spinnerCity.setSelection(i, false)
|
||||
break
|
||||
}
|
||||
}
|
||||
private fun tryApplyCity() {
|
||||
if (cityApplied) return
|
||||
val target = wantedCityId ?: return
|
||||
val count = cityAdapter.count
|
||||
for (i in 0 until count) {
|
||||
if (cityAdapter.getCityId(i) == target) {
|
||||
binding.spinnerCity.setSelection(i, false)
|
||||
cityApplied = true
|
||||
maybeFinishRestoring()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When subdistricts arrive, select the subdistrict
|
||||
viewModel.subdistrictState.observe(this) { state ->
|
||||
if (state is Result.Success) {
|
||||
val subCount = subdistrictAdapter.count
|
||||
for (i in 0 until subCount) {
|
||||
if (subdistrictAdapter.getSubdistrictId(i) == subdistrictId) {
|
||||
binding.spinnerSubdistrict.setSelection(i, false)
|
||||
break
|
||||
}
|
||||
}
|
||||
private fun tryApplySubdistrict() {
|
||||
if (subdistrictApplied) return
|
||||
val target = wantedSubdistrictId ?: return
|
||||
val count = subdistrictAdapter.count
|
||||
for (i in 0 until count) {
|
||||
if (subdistrictAdapter.getSubdistrictId(i) == target) {
|
||||
binding.spinnerSubdistrict.setSelection(i, false)
|
||||
subdistrictApplied = true
|
||||
maybeFinishRestoring()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryApplyBank() {
|
||||
if (bankApplied) return
|
||||
val targetName = wantedBankName ?: return
|
||||
val pos = bankAdapter.findPositionByName(targetName)
|
||||
if (pos >= 0) {
|
||||
binding.spinnerBankName.setSelection(pos, false)
|
||||
viewModel.bankName.value = targetName
|
||||
viewModel.selectedBankName = targetName
|
||||
validateRequiredFields()
|
||||
bankApplied = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHeader() {
|
||||
binding.header.main.background = ContextCompat.getColor(this, R.color.blue_500).toDrawable()
|
||||
binding.header.headerTitle.visibility = View.GONE
|
||||
@ -301,31 +350,42 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun validateRequiredFields() {
|
||||
val isFormValid = !viewModel.storeName.value.isNullOrBlank() &&
|
||||
!viewModel.street.value.isNullOrBlank() &&
|
||||
(viewModel.postalCode.value ?: 0) > 0 &&
|
||||
!viewModel.subdistrict.value.isNullOrBlank() &&
|
||||
!viewModel.bankName.value.isNullOrBlank() &&
|
||||
(viewModel.bankNumber.value ?: 0) > 0 &&
|
||||
(viewModel.provinceId.value ?: 0) > 0 &&
|
||||
!viewModel.cityId.value.isNullOrBlank() &&
|
||||
(viewModel.storeTypeId.value ?: 0) > 0 &&
|
||||
viewModel.ktpUri != null &&
|
||||
viewModel.nibUri != null &&
|
||||
viewModel.npwpUri != null &&
|
||||
viewModel.selectedCouriers.isNotEmpty() &&
|
||||
!viewModel.accountName.value.isNullOrBlank()
|
||||
val bankName = viewModel.bankName.value?.trim().orEmpty()
|
||||
val bankSelected = bankName.isNotEmpty() && !bankName.equals("Pilih Bank", ignoreCase = true)
|
||||
|
||||
binding.btnRegister.isEnabled = true
|
||||
if (isFormValid) {
|
||||
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_active)
|
||||
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.white))
|
||||
binding.btnRegister.isEnabled = true
|
||||
} else {
|
||||
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_disabled)
|
||||
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.black_300))
|
||||
binding.btnRegister.isEnabled = false
|
||||
val provinceSelected = viewModel.provinceId.value != null
|
||||
val citySelected = !viewModel.cityId.value.isNullOrBlank()
|
||||
val subdistrictSelected = !viewModel.subdistrict.value.isNullOrBlank()
|
||||
|
||||
val currentStoreType = binding.spinnerStoreType.selectedItem as? StoreTypesItem
|
||||
val storeTypeSelected = when {
|
||||
currentStoreType != null -> currentStoreType.id != 0 &&
|
||||
!currentStoreType.name.equals("Pilih Jenis UMKM", true)
|
||||
else -> (viewModel.storeTypeId.value ?: -1) > 0
|
||||
}
|
||||
|
||||
val isFormValid =
|
||||
!viewModel.storeName.value.isNullOrBlank() &&
|
||||
!viewModel.street.value.isNullOrBlank() &&
|
||||
(viewModel.postalCode.value ?: 0) > 0 &&
|
||||
provinceSelected && citySelected && subdistrictSelected &&
|
||||
storeTypeSelected &&
|
||||
bankSelected &&
|
||||
(viewModel.bankNumber.value ?: 0) > 0 &&
|
||||
viewModel.ktpUri != null &&
|
||||
viewModel.nibUri != null &&
|
||||
viewModel.npwpUri != null &&
|
||||
viewModel.selectedCouriers.isNotEmpty() &&
|
||||
!viewModel.accountName.value.isNullOrBlank() &&
|
||||
binding.checkboxApprove.isChecked
|
||||
|
||||
binding.btnRegister.isEnabled = isFormValid
|
||||
binding.btnRegister.setBackgroundResource(
|
||||
if (isFormValid) R.drawable.bg_button_active else R.drawable.bg_button_disabled
|
||||
)
|
||||
binding.btnRegister.setTextColor(
|
||||
ContextCompat.getColor(this, if (isFormValid) R.color.white else R.color.black_300)
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
@ -340,12 +400,10 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
binding.spinnerProvince.isEnabled = false
|
||||
}
|
||||
is Result.Success -> {
|
||||
Log.d(TAG, "setupObservers: Provinces loaded successfully: ${state.data.size} provinces")
|
||||
binding.provinceProgressBar.visibility = View.GONE
|
||||
binding.spinnerProvince.isEnabled = true
|
||||
|
||||
// Update adapter with data
|
||||
provinceAdapter.updateData(state.data)
|
||||
tryApplyProvince()
|
||||
}
|
||||
is Result.Error -> {
|
||||
Log.e(TAG, "setupObservers: Error loading provinces: ${state.exception.message}")
|
||||
@ -364,12 +422,10 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
binding.spinnerCity.isEnabled = false
|
||||
}
|
||||
is Result.Success -> {
|
||||
Log.d(TAG, "setupObservers: Cities loaded successfully: ${state.data.size} cities")
|
||||
binding.cityProgressBar.visibility = View.GONE
|
||||
binding.spinnerCity.isEnabled = true
|
||||
|
||||
// Update adapter with data
|
||||
cityAdapter.updateData(state.data)
|
||||
tryApplyCity()
|
||||
}
|
||||
is Result.Error -> {
|
||||
Log.e(TAG, "setupObservers: Error loading cities: ${state.exception.message}")
|
||||
@ -387,11 +443,20 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
binding.spinnerSubdistrict.isEnabled = false
|
||||
}
|
||||
is Result.Success -> {
|
||||
Log.d(TAG, "setupobservers: Subdistrict loaded successfullti: ${state.data.size} subdistrict")
|
||||
binding.subdistrictProgressBar.visibility = View.GONE
|
||||
binding.spinnerSubdistrict.isEnabled = true
|
||||
|
||||
subdistrictAdapter.updateData(state.data)
|
||||
|
||||
// If you’re not restoring a specific subdistrict, select the first real item
|
||||
if (!isRestoringSelections && state.data.isNotEmpty()) {
|
||||
binding.spinnerSubdistrict.setSelection(0, false)
|
||||
val id0 = subdistrictAdapter.getSubdistrictId(0)
|
||||
viewModel.subdistrict.value = id0 ?: ""
|
||||
viewModel.selectedSubdistrict = id0
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
tryApplySubdistrict()
|
||||
}
|
||||
is Result.Error -> {
|
||||
Log.e(TAG, "setupObservers: Error loading subdistrict: ${state.exception.message}")
|
||||
@ -466,6 +531,7 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
|
||||
// Setup spinner with API data
|
||||
setupStoreTypeSpinner(displayList)
|
||||
tryApplyBank()
|
||||
} else {
|
||||
Log.w(TAG, "setupStoreTypesObserver: Received empty store types list")
|
||||
}
|
||||
@ -510,17 +576,10 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
|
||||
// Set item selection listener
|
||||
binding.spinnerStoreType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val selectedItem = adapter.getItem(position)
|
||||
Log.d(TAG, "Store type selected: position=$position, item=${selectedItem?.name}, id=${selectedItem?.id}")
|
||||
|
||||
if (selectedItem != null && selectedItem.id > 0) {
|
||||
// Store the actual ID from the API, not just position
|
||||
viewModel.storeTypeId.value = selectedItem.id
|
||||
Log.d(TAG, "Set storeTypeId to ${selectedItem.id}")
|
||||
} else {
|
||||
Log.d(TAG, "Default or null store type selected, not setting storeTypeId")
|
||||
}
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
|
||||
val item = (binding.spinnerStoreType.adapter.getItem(pos) as? StoreTypesItem)
|
||||
if (item != null && item.id > 0) viewModel.storeTypeId.value = item.id
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
@ -539,22 +598,12 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
// Setup province spinner
|
||||
binding.spinnerProvince.adapter = provinceAdapter
|
||||
binding.spinnerProvince.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
Log.d(TAG, "Province selected at position: $position")
|
||||
val provinceId = provinceAdapter.getProvinceId(position)
|
||||
if (provinceId != null) {
|
||||
Log.d(TAG, "Setting province ID: $provinceId")
|
||||
viewModel.provinceId.value = provinceId
|
||||
Log.d(TAG, "Fetching cities for province ID: $provinceId")
|
||||
viewModel.getCities(provinceId)
|
||||
|
||||
// Reset city selection when province changes
|
||||
Log.d(TAG, "Clearing city adapter for new province selection")
|
||||
cityAdapter.clear()
|
||||
binding.spinnerCity.setSelection(0)
|
||||
} else {
|
||||
Log.e(TAG, "Invalid province ID for position: $position")
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
|
||||
provinceAdapter.getProvinceId(pos)?.let {
|
||||
viewModel.provinceId.value = it
|
||||
viewModel.getCities(it)
|
||||
}
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
@ -565,21 +614,13 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
// Setup city spinner
|
||||
binding.spinnerCity.adapter = cityAdapter
|
||||
binding.spinnerCity.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
Log.d(TAG, "City selected at position: $position")
|
||||
val cityId = cityAdapter.getCityId(position)
|
||||
if (cityId != null) {
|
||||
Log.d(TAG, "Setting city ID: $cityId")
|
||||
viewModel.cityId.value = cityId
|
||||
Log.d(TAG, "Fetching subdistrict for city ID: $cityId")
|
||||
viewModel.getSubdistrict(cityId)
|
||||
|
||||
subdistrictAdapter.clear()
|
||||
binding.spinnerSubdistrict.setSelection(0)
|
||||
viewModel.selectedCityId = cityId
|
||||
} else {
|
||||
Log.e(TAG, "Invalid city ID for position: $position")
|
||||
override fun onItemSelected(p: AdapterView<*>?, v: View?, pos: Int, id: Long) {
|
||||
cityAdapter.getCityId(pos)?.let {
|
||||
viewModel.cityId.value = it
|
||||
viewModel.getSubdistrict(it)
|
||||
viewModel.selectedCityId = it
|
||||
}
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
@ -590,16 +631,11 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
//Setup Subdistrict spinner
|
||||
binding.spinnerSubdistrict.adapter = subdistrictAdapter
|
||||
binding.spinnerSubdistrict.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
Log.d(TAG, "Subdistrict selected at position: $position")
|
||||
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
|
||||
if (subdistrictId != null) {
|
||||
Log.d(TAG, "Setting subdistrict ID: $subdistrictId")
|
||||
viewModel.subdistrict.value = subdistrictId
|
||||
viewModel.selectedSubdistrict = subdistrictId
|
||||
} else {
|
||||
Log.e(TAG, "Invalid subdistrict ID for position: $position")
|
||||
}
|
||||
override fun onItemSelected(p: AdapterView<*>?, v: View?, pos: Int, id: Long) {
|
||||
val selectedId = subdistrictAdapter.getSubdistrictId(pos)
|
||||
viewModel.subdistrict.value = selectedId ?: "" // empty => not selected
|
||||
viewModel.selectedSubdistrict = selectedId
|
||||
validateRequiredFields()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
@ -609,63 +645,31 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
|
||||
binding.spinnerBankName.adapter = bankAdapter
|
||||
binding.spinnerBankName.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(
|
||||
parent: AdapterView<*>?,
|
||||
view: View?,
|
||||
position: Int,
|
||||
id: Long
|
||||
) {
|
||||
Log.d(TAG, "Bank selected at position: $position")
|
||||
val bankName = bankAdapter.getBankName(position)
|
||||
if (bankName != null) {
|
||||
Log.d(TAG, "Setting bank name: $bankName")
|
||||
viewModel.bankName.value = bankName
|
||||
viewModel.selectedBankName = bankName
|
||||
|
||||
// Optional: Log the selected bank details
|
||||
val selectedBank = bankAdapter.getBankItem(position)
|
||||
selectedBank?.let {
|
||||
Log.d(TAG, "Selected bank: ${it.bankName} (Code: ${it.bankCode})")
|
||||
}
|
||||
|
||||
// Hide progress bar if it was showing
|
||||
binding.bankNameProgressBar.visibility = View.GONE
|
||||
|
||||
} else {
|
||||
Log.e(TAG, "Invalid bank name for position: $position")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
Log.d(TAG, "No bank selected")
|
||||
viewModel.selectedBankName = null
|
||||
override fun onItemSelected(p: AdapterView<*>?, v: View?, pos: Int, id: Long) {
|
||||
val bankName = bankAdapter.getBankName(pos)
|
||||
viewModel.bankName.value = bankName
|
||||
viewModel.selectedBankName = bankName
|
||||
validateRequiredFields()
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) { /* no-op */ }
|
||||
}
|
||||
tryApplyBank()
|
||||
|
||||
// Add initial hints to the spinners
|
||||
if (provinceAdapter.isEmpty) {
|
||||
Log.d(TAG, "Adding default province hint")
|
||||
provinceAdapter.add("Pilih Provinsi")
|
||||
}
|
||||
|
||||
if (cityAdapter.isEmpty) {
|
||||
Log.d(TAG, "Adding default city hint")
|
||||
cityAdapter.add("Pilih Kabupaten/Kota")
|
||||
}
|
||||
|
||||
if (subdistrictAdapter.isEmpty) {
|
||||
Log.d(TAG, "Adding default kecamatan hint")
|
||||
subdistrictAdapter.add("Pilih Kecamatan")
|
||||
}
|
||||
|
||||
if (bankAdapter.isEmpty) {
|
||||
Log.d(TAG, "Adding default bank hint")
|
||||
bankAdapter.add("Pilih Bank")
|
||||
}
|
||||
// // Add initial hints to the spinners
|
||||
// if (provinceAdapter.isEmpty) provinceAdapter.add("Pilih Provinsi")
|
||||
// if (cityAdapter.isEmpty) cityAdapter.add("Pilih Kabupaten/Kota")
|
||||
// if (subdistrictAdapter.isEmpty) subdistrictAdapter.add("Pilih Kecamatan")
|
||||
// if (bankAdapter.isEmpty) bankAdapter.add("Pilih Bank")
|
||||
|
||||
Log.d(TAG, "setupSpinners: Province and city spinners setup completed")
|
||||
}
|
||||
|
||||
private fun maybeFinishRestoring() {
|
||||
if (provinceApplied && cityApplied && subdistrictApplied) {
|
||||
isRestoringSelections = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDocumentUploads() {
|
||||
Log.d(TAG, "setupDocumentUploads: Setting up document upload buttons")
|
||||
|
||||
@ -805,6 +809,7 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
viewModel.storeDescription.value = s.toString()
|
||||
Log.d(TAG, "Store description updated: ${s.toString().take(20)}${if ((s?.length ?: 0) > 20) "..." else ""}")
|
||||
validateRequiredFields()
|
||||
}
|
||||
})
|
||||
|
||||
@ -822,14 +827,8 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
try {
|
||||
viewModel.postalCode.value = s.toString().toInt()
|
||||
Log.d(TAG, "Postal code updated: ${s.toString()}")
|
||||
} catch (e: NumberFormatException) {
|
||||
// Handle invalid input
|
||||
Log.e(TAG, "Invalid postal code input: ${s.toString()}, error: $e")
|
||||
validateRequiredFields()
|
||||
}
|
||||
viewModel.postalCode.value = s.toString().toIntOrNull() ?: 0
|
||||
validateRequiredFields()
|
||||
}
|
||||
})
|
||||
|
||||
@ -839,6 +838,7 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
viewModel.addressDetail.value = s.toString()
|
||||
Log.d(TAG, "Address detail updated: ${s.toString()}")
|
||||
validateRequiredFields()
|
||||
}
|
||||
})
|
||||
|
||||
@ -993,30 +993,41 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun doUpdateStoreProfile() {
|
||||
val nameBody: RequestBody = (viewModel.storeName.value ?: "")
|
||||
.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val typeBody: RequestBody = ((viewModel.storeTypeId.value ?: 0).toString())
|
||||
.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val descBody: RequestBody = (viewModel.storeDescription.value ?: "")
|
||||
.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val onLeaveBody: RequestBody = "false"
|
||||
.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
// --- Text parts ---
|
||||
val nameBody: RequestBody = (viewModel.storeName.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val typeBody: RequestBody = ((viewModel.storeTypeId.value ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val descBody: RequestBody = (viewModel.storeDescription.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
// NOTE: is_on_leave is NOT part of approval multipart; keep separate if needed
|
||||
|
||||
// --- Build Multipart for store image (optional) ---
|
||||
// Prefer compressing images to keep payload small; fall back to raw copy if needed.
|
||||
val latBody: RequestBody = (viewModel.latitude.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val longBody: RequestBody = (viewModel.longitude.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val provBody: RequestBody = ((viewModel.provinceId.value ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val cityBody: RequestBody = (viewModel.cityId.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val subdistrictBody: RequestBody = (viewModel.selectedSubdistrict ?: viewModel.subdistrict.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
|
||||
// If you don't have village picker yet, send empty string or reuse subdistrict
|
||||
val villageBody: RequestBody = "".toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
|
||||
val streetBody: RequestBody = (viewModel.street.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val postalBody: RequestBody = ((viewModel.postalCode.value ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val detailBody: RequestBody = (viewModel.addressDetail.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
|
||||
// You can read user phone from current store profile when reapply
|
||||
val currentPhone = myStoreViewModel.myStoreProfile.value?.store?.userPhone ?: ""
|
||||
val userPhoneBody: RequestBody = currentPhone.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
|
||||
// --- Multipart images/docs (safe compress/copy) ---
|
||||
val storeImgPart: MultipartBody.Part? = viewModel.storeImageUri?.let { uri ->
|
||||
try {
|
||||
// (A) Optional safety check: only allow jpg/png/webp
|
||||
val allowed = Regex("^(jpg|jpeg|png|webp)$", RegexOption.IGNORE_CASE)
|
||||
if (!ImageUtils.isAllowedFileType(this, uri, allowed)) {
|
||||
Toast.makeText(this, "Format gambar tidak didukung", Toast.LENGTH_SHORT).show()
|
||||
null
|
||||
} else {
|
||||
// (B) Compress for upload (ke cacheDir), then build multipart
|
||||
val compressed: File = ImageUtils.compressImage(
|
||||
context = this,
|
||||
uri = uri,
|
||||
filename = "storeimg", // prefix
|
||||
filename = "storeimg",
|
||||
maxWidth = 1024,
|
||||
maxHeight = 1024,
|
||||
quality = 80
|
||||
@ -1024,18 +1035,76 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
FileUtils.createMultipartFromFile("storeimg", compressed)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// If compression fails, try raw copy as fallback
|
||||
val rawFile = FileUtils.createTempFileFromUri(this, uri)
|
||||
rawFile?.let { FileUtils.createMultipartFromFile("storeimg", it) }
|
||||
}
|
||||
}
|
||||
|
||||
myStoreViewModel.updateStoreProfile(
|
||||
val ktpPart: MultipartBody.Part? = viewModel.ktpUri?.let { uri ->
|
||||
val file = FileUtils.createTempFileFromUri(this, uri)
|
||||
file?.let { FileUtils.createMultipartFromFile("ktp", it) }
|
||||
}
|
||||
|
||||
val npwpPart: MultipartBody.Part? = viewModel.npwpUri?.let { uri ->
|
||||
val file = FileUtils.createTempFileFromUri(this, uri)
|
||||
file?.let { FileUtils.createMultipartFromFile("npwp", it) }
|
||||
}
|
||||
|
||||
val nibPart: MultipartBody.Part? = viewModel.nibUri?.let { uri ->
|
||||
val file = FileUtils.createTempFileFromUri(this, uri)
|
||||
file?.let { FileUtils.createMultipartFromFile("nib", it) }
|
||||
}
|
||||
|
||||
// --- Couriers desired (sync to exactly this set) ---
|
||||
val desiredCouriers = viewModel.selectedCouriers.toList()
|
||||
|
||||
// --- (Optional) Payment upsert from UI fields ---
|
||||
// If you want to send the bank from the form during re-apply:
|
||||
val paymentsToUpsert = buildList {
|
||||
val bankName = viewModel.bankName.value
|
||||
val bankNum = viewModel.bankNumber.value?.toString()
|
||||
val accName = viewModel.accountName.value
|
||||
|
||||
if (!bankName.isNullOrBlank() && !bankNum.isNullOrBlank() && !accName.isNullOrBlank()) {
|
||||
// If you want to update the first existing payment instead of adding new:
|
||||
val existingId = myStoreViewModel.payment.value?.firstOrNull()?.id
|
||||
add(
|
||||
PaymentUpdate(
|
||||
id = existingId, // null => add; id!=null => update
|
||||
bankName = bankName,
|
||||
bankNum = bankNum,
|
||||
accountName = accName,
|
||||
qrisImage = null // attach File if you have new QRIS to upload
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Delete list (empty if none) ---
|
||||
val paymentIdToDelete = emptyList<Int>()
|
||||
|
||||
// --- Fire the update ---
|
||||
myStoreViewModel.updateStoreApproval(
|
||||
storeName = nameBody,
|
||||
storeType = typeBody,
|
||||
description = descBody,
|
||||
isOnLeave = onLeaveBody,
|
||||
storeImage = storeImgPart
|
||||
storeType = typeBody,
|
||||
latitude = latBody,
|
||||
longitude = longBody,
|
||||
storeProvince = provBody,
|
||||
storeCity = cityBody,
|
||||
storeSubdistrict = subdistrictBody,
|
||||
storeVillage = villageBody,
|
||||
storeStreet = streetBody,
|
||||
storePostalCode = postalBody,
|
||||
storeAddressDetail = detailBody,
|
||||
userPhone = userPhoneBody,
|
||||
paymentsToUpdate = paymentsToUpsert,
|
||||
paymentIdToDelete = paymentIdToDelete,
|
||||
storeCourier = desiredCouriers,
|
||||
storeImage = storeImgPart,
|
||||
ktpImage = ktpPart,
|
||||
npwpDocument = npwpPart,
|
||||
nibDocument = nibPart
|
||||
)
|
||||
|
||||
myStoreViewModel.updateStoreProfileResult.observe(this) {
|
||||
@ -1049,6 +1118,7 @@ class RegisterStoreActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RegisterStoreActivity"
|
||||
}
|
||||
|
||||
@ -49,6 +49,9 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
||||
private var productId: Int? = null
|
||||
private var hasImage: Boolean = false
|
||||
|
||||
private var isEditing = false
|
||||
private var hasExistingImage = false
|
||||
|
||||
private val viewModel: ProductViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
sessionManager = SessionManager(this)
|
||||
@ -98,7 +101,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
||||
binding = ActivityDetailStoreProductBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val isEditing = intent.getBooleanExtra("is_editing", false)
|
||||
isEditing = intent.getBooleanExtra("is_editing", false)
|
||||
productId = intent.getIntExtra("product_id", -1)
|
||||
|
||||
binding.headerStoreProduct.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk"
|
||||
@ -179,6 +182,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
||||
binding.btnRemoveFoto.setOnClickListener {
|
||||
imageUri = null
|
||||
hasImage = false
|
||||
hasExistingImage = false
|
||||
binding.switcherFotoProduk.showPrevious()
|
||||
validateForm()
|
||||
}
|
||||
@ -236,7 +240,9 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
||||
} else product.image
|
||||
Glide.with(this).load(imageUrl).into(binding.ivPreviewFoto)
|
||||
binding.switcherFotoProduk.showNext()
|
||||
|
||||
hasImage = true
|
||||
hasExistingImage = true
|
||||
|
||||
// SPPIRT
|
||||
product.sppirt?.let {
|
||||
@ -331,10 +337,18 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
||||
val duration = binding.edtDurasi.text.toString().trim()
|
||||
val wholesaleMinItem = binding.edtMinPesanGrosir.text.toString().trim()
|
||||
val wholesalePrice = binding.edtHargaGrosir.text.toString().trim()
|
||||
val category = binding.spinnerKategoriProduk.selectedItemPosition != -1
|
||||
val categorySelected = binding.spinnerKategoriProduk.selectedItemPosition != -1
|
||||
val isPreOrderChecked = binding.switchIsPreOrder.isChecked
|
||||
val isWholesaleChecked = binding.switchIsWholesale.isChecked
|
||||
|
||||
val hasRequiredImage = if (isEditing) {
|
||||
// In edit mode: allow existing server image OR newly picked image
|
||||
hasImage || hasExistingImage
|
||||
} else {
|
||||
// In create mode: must have a picked image
|
||||
hasImage
|
||||
}
|
||||
|
||||
val valid = name.isNotEmpty() &&
|
||||
description.isNotEmpty() &&
|
||||
price.isNotEmpty() &&
|
||||
@ -343,8 +357,8 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
||||
weight.isNotEmpty() &&
|
||||
(!isPreOrderChecked || duration.isNotEmpty()) &&
|
||||
(!isWholesaleChecked || (wholesaleMinItem.isNotEmpty() && wholesalePrice.isNotEmpty())) &&
|
||||
category &&
|
||||
hasImage
|
||||
categorySelected &&
|
||||
hasRequiredImage
|
||||
|
||||
binding.btnSaveProduct.isEnabled = valid
|
||||
binding.btnSaveProduct.setTextColor(
|
||||
|
||||
@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.Store
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem
|
||||
@ -19,6 +20,7 @@ import kotlinx.coroutines.launch
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
@ -199,6 +201,80 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toRequestBody(): RequestBody =
|
||||
RequestBody.create("text/plain".toMediaTypeOrNull(), this)
|
||||
private fun String.toPlain(): RequestBody =
|
||||
this.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
|
||||
fun updateStoreApproval(
|
||||
storeName: RequestBody,
|
||||
description: RequestBody,
|
||||
storeType: RequestBody,
|
||||
latitude: RequestBody,
|
||||
longitude: RequestBody,
|
||||
storeProvince: RequestBody,
|
||||
storeCity: RequestBody,
|
||||
storeSubdistrict: RequestBody,
|
||||
storeVillage: RequestBody,
|
||||
storeStreet: RequestBody,
|
||||
storePostalCode: RequestBody,
|
||||
storeAddressDetail: RequestBody,
|
||||
userPhone: RequestBody,
|
||||
paymentsToUpdate: List<PaymentUpdate> = emptyList(),
|
||||
paymentIdToDelete: List<Int> = emptyList(),
|
||||
storeCourier: List<String>? = null,
|
||||
storeImage: MultipartBody.Part?,
|
||||
ktpImage: MultipartBody.Part?,
|
||||
npwpDocument: MultipartBody.Part?,
|
||||
nibDocument: MultipartBody.Part?
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val store = myStoreProfile.value
|
||||
|
||||
if (store == null) {
|
||||
_errorMessage.postValue("Data toko tidak tersedia")
|
||||
Log.e(TAG, "Store data is null")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val response = repository.updateStoreApproval(
|
||||
storeName = storeName,
|
||||
description = description,
|
||||
storeType = storeType,
|
||||
latitude = latitude,
|
||||
longitude = longitude,
|
||||
storeProvince = storeProvince,
|
||||
storeCity = storeCity,
|
||||
storeSubdistrict = storeSubdistrict,
|
||||
storeVillage = storeVillage,
|
||||
storeStreet = storeStreet,
|
||||
storePostalCode = storePostalCode,
|
||||
storeAddressDetail = storeAddressDetail,
|
||||
userPhone = userPhone,
|
||||
paymentsToUpdate = paymentsToUpdate,
|
||||
paymentIdToDelete = paymentIdToDelete,
|
||||
storeCourier = storeCourier,
|
||||
storeImage = storeImage,
|
||||
ktpImage = ktpImage,
|
||||
npwpDocument = npwpDocument,
|
||||
nibDocument = nibDocument
|
||||
)
|
||||
|
||||
if (response != null) {
|
||||
if (response.isSuccessful) {
|
||||
_updateStoreProfileResult.postValue(response.body())
|
||||
Log.d(TAG, "Update successful: ${response.body()}")
|
||||
} else {
|
||||
_errorMessage.postValue("Gagal memperbarui profil")
|
||||
Log.e(TAG, "Update failed: ${response.errorBody()?.string()}")
|
||||
}
|
||||
} else {
|
||||
_errorMessage.postValue("Terjadi kesalahan jaringan atau server")
|
||||
Log.e(TAG, "Repository returned null response")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.postValue(e.message ?: "Unexpected error")
|
||||
Log.e(TAG, "Exception updating store profile", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -371,6 +371,7 @@
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:hint="Isi stok produk di sini"
|
||||
android:padding="8dp"
|
||||
android:inputType="number"
|
||||
style="@style/body_small" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -413,6 +414,7 @@
|
||||
android:hint="Isi minimum pemesanan produk di sini"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:inputType="number"
|
||||
android:layout_marginTop="10dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -711,6 +713,7 @@
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:hint="Isi minimum produk untuk mendapatkan harga grosir di sini"
|
||||
android:padding="8dp"
|
||||
android:inputType="number"
|
||||
style="@style/body_small" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -543,49 +543,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Bank -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="10. Bank"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="*"
|
||||
style="@style/body_medium"
|
||||
android:textColor="@color/red_required"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_bank_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:hint="Isi nama bank untuk toko Anda di sini"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Nama Bank-->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -654,6 +611,48 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Nama Pemilik Rekening -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="12. Nama Pemilik Rekening"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="*"
|
||||
style="@style/body_medium"
|
||||
android:textColor="@color/red_required"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_account_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:hint="Isi nama pemilik rekening"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Nomor Rekening -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -697,48 +696,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Nama Pemilik Rekening -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="12. Nama Pemilik Rekening"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="*"
|
||||
style="@style/body_medium"
|
||||
android:textColor="@color/red_required"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_account_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:hint="Isi nama pemilik rekening"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Kurir -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -915,73 +872,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- NIB -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="16. Dokumen NIB"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="*"
|
||||
style="@style/body_medium"
|
||||
android:textColor="@color/red_required"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container_nib"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@android:drawable/editbox_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_nib"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_upload_nib"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_menu_upload" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Unggah dokumen Anda di sini"
|
||||
android:textColor="#777777" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- NPWP -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -1049,6 +939,73 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- NIB -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="16. Dokumen NIB"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="*"
|
||||
style="@style/body_medium"
|
||||
android:textColor="@color/red_required"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container_nib"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@android:drawable/editbox_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_nib"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_upload_nib"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_menu_upload" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Unggah dokumen Anda di sini"
|
||||
android:textColor="#777777" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
Reference in New Issue
Block a user