This commit is contained in:
Gracia
2025-04-12 04:40:19 +07:00
parent e9e3597363
commit 4e82a36cad
7 changed files with 471 additions and 147 deletions

View File

@ -14,13 +14,17 @@ import com.alya.ecommerce_serang.data.api.response.RegisterResponse
import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse
import com.alya.ecommerce_serang.data.api.response.StoreResponse import com.alya.ecommerce_serang.data.api.response.StoreResponse
import com.alya.ecommerce_serang.data.api.response.ViewStoreProductsResponse import com.alya.ecommerce_serang.data.api.response.ViewStoreProductsResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path import retrofit2.http.Path
interface ApiService { interface ApiService {
@ -74,19 +78,22 @@ interface ApiService {
@GET("category") @GET("category")
fun getCategories(): Call<CategoryResponse> fun getCategories(): Call<CategoryResponse>
@Multipart
@POST("store/createproduct") @POST("store/createproduct")
@FormUrlEncoded
suspend fun addProduct( suspend fun addProduct(
@Field("name") name: String, @Part("name") name: RequestBody,
@Field("description") description: String, @Part("description") description: RequestBody,
@Field("price") price: Int, @Part("price") price: RequestBody,
@Field("stock") stock: Int, @Part("stock") stock: RequestBody,
@Field("min_order") minOrder: Int, @Part("min_order") minOrder: RequestBody,
@Field("weight") weight: Int, @Part("weight") weight: RequestBody,
@Field("is_pre_order") isPreOrder: Boolean, @Part("is_pre_order") isPreOrder: RequestBody,
@Field("duration") duration: Int, @Part("duration") duration: RequestBody,
@Field("category_id") categoryId: Int, @Part("category_id") categoryId: RequestBody,
@Field("is_active") isActive: String @Part("is_active") isActive: RequestBody,
@Part image: MultipartBody.Part?,
@Part sppirt: MultipartBody.Part?,
@Part halal: MultipartBody.Part?
): Response<Unit> ): Response<Unit>
} }

View File

@ -8,6 +8,10 @@ import com.alya.ecommerce_serang.data.api.response.ReviewsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import java.io.File
class ProductRepository(private val apiService: ApiService) { class ProductRepository(private val apiService: ApiService) {
suspend fun getAllProducts(): Result<List<ProductsItem>> = suspend fun getAllProducts(): Result<List<ProductsItem>> =
@ -81,13 +85,17 @@ class ProductRepository(private val apiService: ApiService) {
} }
suspend fun fetchMyStoreProducts(): List<ProductsItem> { suspend fun fetchMyStoreProducts(): List<ProductsItem> {
return try {
val response = apiService.getStoreProduct() val response = apiService.getStoreProduct()
if (response.isSuccessful) { if (response.isSuccessful) {
val responseBody = response.body() response.body()?.products?.filterNotNull() ?: emptyList()
return responseBody?.products?.filterNotNull() ?: emptyList()
} else { } else {
throw Exception("Failed to fetch store products: ${response.message()}") throw Exception("Failed to fetch store products: ${response.message()}")
} }
} catch (e: Exception) {
Log.e("ProductRepository", "Error fetching store products", e)
throw e
}
} }
suspend fun addProduct( suspend fun addProduct(
@ -100,33 +108,50 @@ class ProductRepository(private val apiService: ApiService) {
isPreOrder: Boolean, isPreOrder: Boolean,
duration: Int, duration: Int,
categoryId: Int, categoryId: Int,
isActive: Boolean isActive: Boolean,
image: File?,
sppirt: File?,
halal: File?
): Result<Unit> = withContext(Dispatchers.IO) { ): Result<Unit> = withContext(Dispatchers.IO) {
try { try {
val status = if (isActive) "active" else "inactive" val namePart = RequestBody.create("text/plain".toMediaTypeOrNull(), name)
val descriptionPart = RequestBody.create("text/plain".toMediaTypeOrNull(), description)
val pricePart = RequestBody.create("text/plain".toMediaTypeOrNull(), price.toString())
val stockPart = RequestBody.create("text/plain".toMediaTypeOrNull(), stock.toString())
val minOrderPart = RequestBody.create("text/plain".toMediaTypeOrNull(), minOrder.toString())
val weightPart = RequestBody.create("text/plain".toMediaTypeOrNull(), weight.toString())
val isPreOrderPart = RequestBody.create("text/plain".toMediaTypeOrNull(), isPreOrder.toString())
val durationPart = RequestBody.create("text/plain".toMediaTypeOrNull(), duration.toString())
val categoryIdPart = RequestBody.create("text/plain".toMediaTypeOrNull(), categoryId.toString())
val isActivePart = RequestBody.create("text/plain".toMediaTypeOrNull(), if (isActive) "1" else "0")
val imagePart = image?.let {
val req = RequestBody.create("image/*".toMediaTypeOrNull(), it)
MultipartBody.Part.createFormData("image", it.name, req)
}
val sppirtPart = sppirt?.let {
val req = RequestBody.create("application/pdf".toMediaTypeOrNull(), it)
MultipartBody.Part.createFormData("sppirt", it.name, req)
}
val halalPart = halal?.let {
val req = RequestBody.create("application/pdf".toMediaTypeOrNull(), it)
MultipartBody.Part.createFormData("halal", it.name, req)
}
val response = apiService.addProduct( val response = apiService.addProduct(
name = name, namePart, descriptionPart, pricePart, stockPart, minOrderPart,
description = description, weightPart, isPreOrderPart, durationPart, categoryIdPart, isActivePart,
price = price, imagePart, sppirtPart, halalPart
stock = stock,
minOrder = minOrder,
weight = weight,
isPreOrder = isPreOrder,
duration = duration,
categoryId = categoryId,
isActive = status
) )
if (response.isSuccessful) { if (response.isSuccessful) Result.Success(Unit)
Result.Success(Unit) else Result.Error(Exception("Failed: ${response.code()}"))
} else {
Result.Error(Exception("Failed to add product. Code: ${response.code()}"))
}
} catch (e: Exception) { } catch (e: Exception) {
Result.Error(e) Result.Error(e)
} }
} }
} }
// suspend fun fetchStoreDetail(storeId: Int): Store? { // suspend fun fetchStoreDetail(storeId: Int): Store? {

View File

@ -1,11 +1,16 @@
package com.alya.ecommerce_serang.ui.profile.mystore.product package com.alya.ecommerce_serang.ui.profile.mystore.product
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -18,6 +23,11 @@ import com.alya.ecommerce_serang.databinding.ActivityStoreProductDetailBinding
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import java.io.File
import kotlin.getValue import kotlin.getValue
class StoreProductDetailActivity : AppCompatActivity() { class StoreProductDetailActivity : AppCompatActivity() {
@ -25,6 +35,9 @@ class StoreProductDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityStoreProductDetailBinding private lateinit var binding: ActivityStoreProductDetailBinding
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private lateinit var categoryList: List<CategoryItem> private lateinit var categoryList: List<CategoryItem>
private var imageUri: Uri? = null
private var sppirtUri: Uri? = null
private var halalUri: Uri? = null
private val viewModel: ProductViewModel by viewModels { private val viewModel: ProductViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
@ -35,14 +48,50 @@ class StoreProductDetailActivity : AppCompatActivity() {
} }
} }
private val imagePickerLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
imageUri = result.data?.data
imageUri?.let {
binding.ivPreviewFoto.setImageURI(it)
binding.switcherFotoProduk.showNext()
}
validateForm()
}
}
private val sppirtLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri != null && isValidFile(uri)) {
sppirtUri = uri
binding.tvSppirtName.text = File(uri.path ?: "").name
binding.switcherSppirt.showNext()
}
}
private val halalLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri != null && isValidFile(uri)) {
halalUri = uri
binding.tvHalalName.text = File(uri.path ?: "").name
binding.switcherHalal.showNext()
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityStoreProductDetailBinding.inflate(layoutInflater) binding = ActivityStoreProductDetailBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setupHeader() setupHeader()
observeCategories()
// Fetch categories
viewModel.loadCategories() viewModel.loadCategories()
viewModel.categoryList.observe(this) { result ->
if (result is Result.Success) {
categoryList = result.data
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, categoryList.map { it.name })
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerKategoriProduk.adapter = adapter
}
}
// Setup Pre-Order visibility // Setup Pre-Order visibility
binding.switchIsPreOrder.setOnCheckedChangeListener { _, isChecked -> binding.switchIsPreOrder.setOnCheckedChangeListener { _, isChecked ->
@ -50,71 +99,123 @@ class StoreProductDetailActivity : AppCompatActivity() {
validateForm() validateForm()
} }
setupFormValidation() binding.tvTambahFoto.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK).apply { type = "image/*" }
imagePickerLauncher.launch(intent)
}
binding.layoutUploadFoto.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK).apply { type = "image/*" }
imagePickerLauncher.launch(intent)
}
binding.layoutUploadSppirt.setOnClickListener { sppirtLauncher.launch("*/*") }
binding.btnRemoveSppirt.setOnClickListener {
sppirtUri = null
binding.switcherSppirt.showPrevious()
}
binding.layoutUploadHalal.setOnClickListener { halalLauncher.launch("*/*") }
binding.btnRemoveHalal.setOnClickListener {
halalUri = null
binding.switcherHalal.showPrevious()
}
binding.btnRemoveFoto.setOnClickListener {
imageUri = null
binding.switcherFotoProduk.showPrevious()
validateForm()
}
val watcher = object : TextWatcher {
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?) { validateForm() }
}
listOf(
binding.edtNamaProduk,
binding.edtDeskripsiProduk,
binding.edtHargaProduk,
binding.edtStokProduk,
binding.edtMinOrder,
binding.edtBeratProduk,
binding.edtDurasi
).forEach { it.addTextChangedListener(watcher) }
validateForm() validateForm()
binding.btnSaveProduct.setOnClickListener { binding.btnSaveProduct.setOnClickListener {
if (binding.btnSaveProduct.isEnabled) addProduct() if (!binding.btnSaveProduct.isEnabled) {
focusFirstInvalidField()
return@setOnClickListener
}
submitProduct()
} }
} }
private fun setupHeader() { private fun isValidFile(uri: Uri): Boolean {
binding.header.headerTitle.text = "Tambah Produk" val mimeType = contentResolver.getType(uri) ?: return false
return listOf("application/pdf", "image/jpeg", "image/png", "image/jpg").contains(mimeType)
}
binding.header.headerLeftIcon.setOnClickListener { private fun validateForm() {
onBackPressedDispatcher.onBackPressed() val valid = binding.edtNamaProduk.text.isNotBlank() &&
binding.edtDeskripsiProduk.text.isNotBlank() &&
binding.edtHargaProduk.text.isNotBlank() &&
binding.edtStokProduk.text.isNotBlank() &&
binding.edtMinOrder.text.isNotBlank() &&
binding.edtBeratProduk.text.isNotBlank() &&
(!binding.switchIsPreOrder.isChecked || binding.edtDurasi.text.isNotBlank()) &&
imageUri != null
binding.btnSaveProduct.isEnabled = valid
binding.btnSaveProduct.setTextColor(
if (valid) ContextCompat.getColor(this, R.color.white) else ContextCompat.getColor(this, R.color.black_300)
)
binding.btnSaveProduct.setBackgroundResource(
if (valid) R.drawable.bg_button_active else R.drawable.bg_button_disabled
)
}
private fun focusFirstInvalidField() {
when {
binding.edtNamaProduk.text.isBlank() -> binding.edtNamaProduk.requestFocus()
binding.edtDeskripsiProduk.text.isBlank() -> binding.edtDeskripsiProduk.requestFocus()
binding.edtHargaProduk.text.isBlank() -> binding.edtHargaProduk.requestFocus()
binding.edtStokProduk.text.isBlank() -> binding.edtStokProduk.requestFocus()
binding.edtMinOrder.text.isBlank() -> binding.edtMinOrder.requestFocus()
binding.edtBeratProduk.text.isBlank() -> binding.edtBeratProduk.requestFocus()
binding.switchIsPreOrder.isChecked && binding.edtDurasi.text.isBlank() -> binding.edtDurasi.requestFocus()
imageUri == null -> Toast.makeText(this, "Silakan unggah foto produk", Toast.LENGTH_SHORT).show()
} }
} }
private fun observeCategories() { private fun uriToFile(uri: Uri, context: Context): File {
viewModel.categoryList.observe(this) { result -> val inputStream = context.contentResolver.openInputStream(uri)
when (result) { val file = File.createTempFile("upload_", ".tmp", context.cacheDir)
is Result.Loading -> { inputStream?.use { input -> file.outputStream().use { input.copyTo(it) } }
// Optionally show loading spinner return file
}
is Result.Success -> {
categoryList = result.data
setupCategorySpinner(categoryList)
}
is Result.Error -> {
Toast.makeText(
this,
"Failed to load categories: ${result.exception.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
} }
private fun setupCategorySpinner(categories: List<CategoryItem>) { private fun submitProduct() {
val categoryNames = categories.map { it.name }
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, categoryNames)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerKategoriProduk.adapter = adapter
}
private fun addProduct() {
val name = binding.edtNamaProduk.text.toString() val name = binding.edtNamaProduk.text.toString()
val description = binding.edtDeskripsiProduk.text.toString() val description = binding.edtDeskripsiProduk.text.toString()
val price = binding.edtHargaProduk.text.toString().toIntOrNull() ?: 0 val price = binding.edtHargaProduk.text.toString().toInt()
val stock = binding.edtStokProduk.text.toString().toIntOrNull() ?: 0 val stock = binding.edtStokProduk.text.toString().toInt()
val minOrder = binding.edtMinOrder.text.toString().toIntOrNull() ?: 1 val minOrder = binding.edtMinOrder.text.toString().toInt()
val weight = binding.edtBeratProduk.text.toString().toIntOrNull() ?: 0 val weight = binding.edtBeratProduk.text.toString().toInt()
val isPreOrder = binding.switchIsPreOrder.isChecked val isPreOrder = binding.switchIsPreOrder.isChecked
val duration = binding.edtDurasi.text.toString().toIntOrNull() ?: 0 val duration = if (isPreOrder) binding.edtDurasi.text.toString().toInt() else 0
val isActive = binding.switchIsActive.isChecked val isActive = binding.switchIsActive.isChecked
val categoryPosition = binding.spinnerKategoriProduk.selectedItemPosition val categoryId = categoryList.getOrNull(binding.spinnerKategoriProduk.selectedItemPosition)?.id ?: 0
val categoryId = categoryList.getOrNull(categoryPosition)?.id ?: 0
if (isPreOrder && duration == 0) { val imageFile = imageUri?.let { uriToFile(it, this) }
Toast.makeText(this, "Durasi wajib diisi jika pre-order diaktifkan.", Toast.LENGTH_SHORT).show() val sppirtFile = sppirtUri?.let { uriToFile(it, this) }
return val halalFile = halalUri?.let { uriToFile(it, this) }
}
viewModel.addProduct( viewModel.addProduct(
name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, isActive name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, isActive, imageFile, sppirtFile, halalFile
).observe(this) { result -> ).observe(this) { result ->
when (result) { when (result) {
is Result.Loading -> binding.btnSaveProduct.isEnabled = false is Result.Loading -> binding.btnSaveProduct.isEnabled = false
@ -123,53 +224,15 @@ class StoreProductDetailActivity : AppCompatActivity() {
finish() finish()
} }
is Result.Error -> { is Result.Error -> {
binding.btnSaveProduct.isEnabled = true
Toast.makeText(this, "Gagal: ${result.exception.message}", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gagal: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun setupFormValidation() {
val watcher = object : TextWatcher {
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?) {
validateForm()
}
}
// Watch all fields
binding.edtNamaProduk.addTextChangedListener(watcher)
binding.edtDeskripsiProduk.addTextChangedListener(watcher)
binding.edtHargaProduk.addTextChangedListener(watcher)
binding.edtStokProduk.addTextChangedListener(watcher)
binding.edtMinOrder.addTextChangedListener(watcher)
binding.edtBeratProduk.addTextChangedListener(watcher)
binding.edtDurasi.addTextChangedListener(watcher)
}
private fun validateForm() {
val isNameValid = binding.edtNamaProduk.text.toString().isNotBlank()
val isDescriptionValid = binding.edtDeskripsiProduk.text.toString().isNotBlank()
val isPriceValid = binding.edtHargaProduk.text.toString().isNotBlank()
val isStockValid = binding.edtStokProduk.text.toString().isNotBlank()
val isMinOrderValid = binding.edtMinOrder.text.toString().isNotBlank()
val isWeightValid = binding.edtBeratProduk.text.toString().isNotBlank()
val isPreOrderChecked = binding.switchIsPreOrder.isChecked
val isDurationValid = !isPreOrderChecked || binding.edtDurasi.text.toString().isNotBlank()
val isFormValid = isNameValid && isDescriptionValid && isPriceValid &&
isStockValid && isMinOrderValid && isWeightValid && isDurationValid
if (isFormValid) {
binding.btnSaveProduct.isEnabled = true binding.btnSaveProduct.isEnabled = true
binding.btnSaveProduct.setBackgroundResource(R.drawable.bg_button_active)
binding.btnSaveProduct.setTextColor(ContextCompat.getColor(this, R.color.white))
} else {
binding.btnSaveProduct.isEnabled = false
binding.btnSaveProduct.setBackgroundResource(R.drawable.bg_button_disabled)
binding.btnSaveProduct.setTextColor(ContextCompat.getColor(this, R.color.black_300))
} }
} }
} }
}
private fun setupHeader() {
binding.header.headerTitle.text = "Tambah Produk"
binding.header.headerLeftIcon.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
}
}

View File

@ -13,6 +13,7 @@ import com.alya.ecommerce_serang.data.api.response.ReviewsItem
import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
class ProductViewModel(private val repository: ProductRepository) : ViewModel() { class ProductViewModel(private val repository: ProductRepository) : ViewModel() {
@ -74,17 +75,15 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
isPreOrder: Boolean, isPreOrder: Boolean,
duration: Int, duration: Int,
categoryId: Int, categoryId: Int,
isActive: Boolean isActive: Boolean,
image: File?,
sppirt: File?,
halal: File?
): LiveData<Result<Unit>> = liveData { ): LiveData<Result<Unit>> = liveData {
emit(Result.Loading) emit(Result.Loading)
val result = repository.addProduct( emit(repository.addProduct(name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, isActive, image, sppirt, halal))
name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, isActive
)
emit(result)
} }
// Optional: for store detail if you need it later // Optional: for store detail if you need it later
// fun loadStoreDetail(storeId: Int) { // fun loadStoreDetail(storeId: Int) {
// viewModelScope.launch { // viewModelScope.launch {
@ -92,4 +91,5 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
// _storeDetail.value = storeResult // _storeDetail.value = storeResult
// } // }
// } // }
} }

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="@color/black_100"
android:dashWidth="5dp"
android:dashGap="5dp" />
<solid android:color="@color/white" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

View File

@ -69,15 +69,61 @@
</LinearLayout> </LinearLayout>
<!-- ImageView untuk preview gambar --> <!-- ViewSwitcher untuk Upload Gambar Produk -->
<ViewSwitcher
android:id="@+id/switcher_foto_produk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="24dp">
<!-- Tampilan saat belum ada gambar di-upload -->
<FrameLayout
android:id="@+id/layout_upload_foto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_upload"
android:gravity="center"
android:padding="13dp">
<ImageView
android:layout_width="44dp"
android:layout_height="44dp"
android:src="@drawable/ic_upload"
android:contentDescription="Ikon Upload" />
</FrameLayout>
<!-- Tampilan saat gambar sudah di-upload -->
<FrameLayout
android:layout_width="70dp"
android:layout_height="70dp"
android:background="@drawable/bg_upload"
android:layout_gravity="center">
<ImageView <ImageView
android:id="@+id/iv_preview_foto" android:id="@+id/iv_preview_foto"
android:layout_width="72dp" android:layout_width="match_parent"
android:layout_height="72dp" android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:contentDescription="Preview Gambar" android:contentDescription="Preview Gambar"
android:src="@drawable/ic_upload"/> android:src="@drawable/placeholder_image"/>
<ImageButton
android:id="@+id/btn_remove_foto"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="top|end"
android:layout_margin="6dp"
android:elevation="8dp"
android:src="@drawable/ic_close"
android:contentDescription="Hapus Gambar"
android:padding="4dp"
app:tint="@color/white" />
</FrameLayout>
</ViewSwitcher>
</LinearLayout> </LinearLayout>
@ -245,7 +291,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Harga Produk" android:text="Harga Produk"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginRight="4dp"/> android:layout_marginEnd="4dp"/>
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
@ -409,7 +455,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:background="@drawable/bg_text_field" android:background="@drawable/bg_text_field"
android:gravity="center"> android:gravity="center"
android:layout_marginTop="10dp">
<EditText <EditText
android:id="@+id/edt_berat_produk" android:id="@+id/edt_berat_produk"
@ -549,7 +596,6 @@
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -567,7 +613,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:background="@drawable/bg_text_field" android:background="@drawable/bg_text_field"
android:gravity="center"> android:gravity="center"
android:layout_marginTop="10dp">
<EditText <EditText
android:id="@+id/edt_durasi" android:id="@+id/edt_durasi"
@ -592,6 +639,178 @@
</LinearLayout> </LinearLayout>
<!-- SPPIRT -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<!-- Label SPPIRT -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SPPIRT"
style="@style/body_medium"/>
<!-- ViewSwitcher: Switches antara upload box dan uploaded preview SPPIRT -->
<ViewSwitcher
android:id="@+id/switcher_sppirt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<!-- Upload Prompt Layout -->
<FrameLayout
android:id="@+id/layout_upload_sppirt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_upload"
android:padding="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_upload"
android:contentDescription="Ikon Unggah" />
<TextView
style="@style/body_small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Unggah dokumen SPPIRT Anda di sini"
android:textColor="@color/black_300"
android:layout_marginTop="8dp"/>
</LinearLayout>
</FrameLayout>
<!-- Uploaded File Preview Layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field"
android:gravity="center"
android:layout_marginTop="10dp"
android:padding="8dp">
<TextView
android:id="@+id/tv_sppirt_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="sppirt.pdf"
style="@style/body_small"/>
<ImageButton
android:id="@+id/btn_remove_sppirt"
android:layout_width="12dp"
android:layout_height="12dp"
android:src="@drawable/ic_close"
app:tint="@color/black_300"
android:contentDescription="Hapus unggahan" />
</LinearLayout>
</ViewSwitcher>
</LinearLayout>
<!-- Halal -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<!-- Label Halal -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sertifikasi Halal"
style="@style/body_medium"/>
<!-- ViewSwitcher: Switches antara upload box dan uploaded preview halal -->
<ViewSwitcher
android:id="@+id/switcher_halal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<!-- Upload Prompt Layout -->
<FrameLayout
android:id="@+id/layout_upload_halal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_upload"
android:padding="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_upload"
android:contentDescription="Ikon Unggah" />
<TextView
style="@style/body_small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Unggah dokumen Sertifikasi Halal Anda di sini"
android:textColor="@color/black_300"
android:layout_marginTop="8dp"/>
</LinearLayout>
</FrameLayout>
<!-- Uploaded File Preview Layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field"
android:gravity="center"
android:layout_marginTop="10dp"
android:padding="8dp">
<TextView
android:id="@+id/tv_halal_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="sppirt.pdf"
style="@style/body_small"/>
<ImageButton
android:id="@+id/btn_remove_halal"
android:layout_width="12dp"
android:layout_height="12dp"
android:src="@drawable/ic_close"
app:tint="@color/black_300"
android:contentDescription="Hapus unggahan" />
</LinearLayout>
</ViewSwitcher>
</LinearLayout>
<!-- Produk Aktif --> <!-- Produk Aktif -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"