diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 329ea8a..d01aae7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,7 +18,7 @@ val localProperties = Properties().apply {
android {
namespace = "com.alya.ecommerce_serang"
- compileSdk = 34
+ compileSdk = 35
defaultConfig {
applicationId = "com.alya.ecommerce_serang"
@@ -38,14 +38,14 @@ android {
buildTypes {
release {
- buildConfigField("String",
- "BASE_URL",
- "\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\"")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
+ buildConfigField("String",
+ "BASE_URL",
+ "\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\"")
}
debug {
buildConfigField("String",
@@ -79,6 +79,7 @@ dependencies {
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
+ implementation(libs.androidx.recyclerview)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -94,7 +95,6 @@ dependencies {
implementation("de.hdodenhof:circleimageview:3.1.0")
-
// implementation(libs.hilt.android)
// kapt("com.google.dagger:hilt-compiler:2.48")
//
@@ -103,3 +103,4 @@ dependencies {
// kapt("androidx.hilt:hilt-compiler:1.0.0")
}
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 92511bf..42b49fb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,8 @@
+
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt
index 41a98dc..a8d36e5 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt
@@ -29,7 +29,7 @@ data class Product(
val categoryId: Int? = null,
@field:SerializedName("price")
- val price: String? = null,
+ val price: Int? = null,
@field:SerializedName("name")
val name: String? = null,
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CreateProductResponse.kt
similarity index 80%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CreateProductResponse.kt
index f8fd202..fcd9aec 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CreateProductResponse.kt
@@ -1,6 +1,5 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.product
-import com.alya.ecommerce_serang.data.api.response.product.Product
import com.google.gson.annotations.SerializedName
data class CreateProductResponse(
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt
index e8ddcb0..026c995 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt
@@ -7,6 +7,7 @@ import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
class ApiConfig {
companion object {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
index ceec7e6..1077a9c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
@@ -11,7 +11,10 @@ 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.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
+import com.alya.ecommerce_serang.data.api.response.product.CreateProductResponse
import com.alya.ecommerce_serang.data.api.response.ViewStoreProductsResponse
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
@@ -36,14 +39,14 @@ import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
-import okhttp3.MultipartBody
-import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.HeaderMap
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
@@ -142,20 +145,23 @@ interface ApiService {
@GET("category")
fun getCategories(): Call
+ @Multipart
@POST("store/createproduct")
- @FormUrlEncoded
suspend fun addProduct(
- @Field("name") name: String,
- @Field("description") description: String,
- @Field("price") price: Int,
- @Field("stock") stock: Int,
- @Field("min_order") minOrder: Int,
- @Field("weight") weight: Int,
- @Field("is_pre_order") isPreOrder: Boolean,
- @Field("duration") duration: Int,
- @Field("category_id") categoryId: Int,
- @Field("is_active") isActive: String
- ): Response
+ @Part("name") name: RequestBody,
+ @Part("description") description: RequestBody,
+ @Part("price") price: RequestBody,
+ @Part("stock") stock: RequestBody,
+ @Part("min_order") minOrder: RequestBody,
+ @Part("weight") weight: RequestBody,
+ @Part("is_pre_order") isPreOrder: RequestBody,
+ @Part("duration") duration: RequestBody,
+ @Part("category_id") categoryId: RequestBody,
+ @Part("status") status: RequestBody,
+ @Part image: MultipartBody.Part?,
+ @Part sppirt: MultipartBody.Part?,
+ @Part halal: MultipartBody.Part?
+ ): Response
@GET("cart_item")
suspend fun getCart (): Response
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
index dad6bef..1a8781c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
@@ -4,13 +4,19 @@ import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
+import com.alya.ecommerce_serang.data.api.response.product.CreateProductResponse
import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.Dispatchers
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) {
suspend fun getAllProducts(): Result> =
@@ -131,12 +137,16 @@ class ProductRepository(private val apiService: ApiService) {
}
suspend fun fetchMyStoreProducts(): List {
- val response = apiService.getStoreProduct()
- if (response.isSuccessful) {
- val responseBody = response.body()
- return responseBody?.products?.filterNotNull() ?: emptyList()
- } else {
- throw Exception("Failed to fetch store products: ${response.message()}")
+ return try {
+ val response = apiService.getStoreProduct()
+ if (response.isSuccessful) {
+ response.body()?.products?.filterNotNull() ?: emptyList()
+ } else {
+ throw Exception("Failed to fetch store products: ${response.message()}")
+ }
+ } catch (e: Exception) {
+ Log.e("ProductRepository", "Error fetching store products", e)
+ throw e
}
}
@@ -150,33 +160,39 @@ class ProductRepository(private val apiService: ApiService) {
isPreOrder: Boolean,
duration: Int,
categoryId: Int,
- isActive: Boolean
- ): Result = withContext(Dispatchers.IO) {
- try {
- val status = if (isActive) "active" else "inactive"
+ status: String,
+ imagePart: MultipartBody.Part?,
+ sppirtPart: MultipartBody.Part?,
+ halalPart: MultipartBody.Part?
+ ): Result {
+ return try {
val response = apiService.addProduct(
- name = name,
- description = description,
- price = price,
- stock = stock,
- minOrder = minOrder,
- weight = weight,
- isPreOrder = isPreOrder,
- duration = duration,
- categoryId = categoryId,
- isActive = status
+ name = RequestBody.create("text/plain".toMediaTypeOrNull(), name),
+ description = RequestBody.create("text/plain".toMediaTypeOrNull(), description),
+ price = RequestBody.create("text/plain".toMediaTypeOrNull(), price.toString()),
+ stock = RequestBody.create("text/plain".toMediaTypeOrNull(), stock.toString()),
+ minOrder = RequestBody.create("text/plain".toMediaTypeOrNull(), minOrder.toString()),
+ weight = RequestBody.create("text/plain".toMediaTypeOrNull(), weight.toString()),
+ isPreOrder = RequestBody.create("text/plain".toMediaTypeOrNull(), isPreOrder.toString()),
+ duration = RequestBody.create("text/plain".toMediaTypeOrNull(), duration.toString()),
+ categoryId = RequestBody.create("text/plain".toMediaTypeOrNull(), categoryId.toString()),
+ status = RequestBody.create("text/plain".toMediaTypeOrNull(), status),
+ image = imagePart,
+ sppirt = sppirtPart,
+ halal = halalPart
)
if (response.isSuccessful) {
- Result.Success(Unit)
+ Result.Success(response.body()!!)
} else {
- Result.Error(Exception("Failed to add product. Code: ${response.code()}"))
+ Result.Error(Exception("Failed to create product: ${response.code()}"))
}
} catch (e: Exception) {
Result.Error(e)
}
}
+
companion object {
private const val TAG = "ProductRepository"
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt
index 09ff20c..ec831be 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt
@@ -4,10 +4,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
+import android.widget.PopupMenu
import android.widget.TextView
+import android.widget.Toast
import androidx.core.content.ContextCompat
+import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.Product
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.bumptech.glide.Glide
@@ -22,27 +26,37 @@ class ProductAdapter(
private val tvProductPrice: TextView = itemView.findViewById(R.id.tv_product_price)
private val tvProductStock: TextView = itemView.findViewById(R.id.tv_product_stock)
private val tvProductStatus: TextView = itemView.findViewById(R.id.tv_product_status)
+ private val ivMenu: ImageView = itemView.findViewById(R.id.iv_menu)
fun bind(product: ProductsItem) {
tvProductName.text = product.name
tvProductPrice.text = "Rp${product.price}"
tvProductStock.text = "Stok: ${product.stock}"
- tvProductStatus.text = product.status
- // Change color depending on status
- tvProductStatus.setTextColor(
- ContextCompat.getColor(
- itemView.context,
- if (product.status.equals("active", true))
- R.color.darkblue_500 else R.color.black_500
- )
- )
+ if (product.status.equals("active",true)) {
+ tvProductStatus.text = "Aktif"
+ tvProductStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.darkblue_500))
+ tvProductStatus.background = ContextCompat.getDrawable(itemView.context, R.drawable.bg_product_active)
+ } else {
+ tvProductStatus.text = "Nonaktif"
+ tvProductStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.black_500))
+ tvProductStatus.background = ContextCompat.getDrawable(itemView.context, R.drawable.bg_product_inactive)
+ }
Glide.with(itemView.context)
.load(product.image)
.placeholder(R.drawable.placeholder_image)
.into(ivProduct)
+ ivMenu.setOnClickListener {
+ // Show Bottom Sheet when menu is clicked
+ val bottomSheetFragment = ProductOptionsBottomSheetFragment(product)
+ bottomSheetFragment.show(
+ (itemView.context as FragmentActivity).supportFragmentManager,
+ bottomSheetFragment.tag
+ )
+ }
+
itemView.setOnClickListener {
onItemClick(product)
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt
new file mode 100644
index 0000000..f2d348a
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt
@@ -0,0 +1,46 @@
+package com.alya.ecommerce_serang.ui.profile.mystore.product
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.ProductsItem
+import com.alya.ecommerce_serang.databinding.FragmentProductOptionsBottomSheetBinding
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+
+class ProductOptionsBottomSheetFragment(private val product: ProductsItem) : BottomSheetDialogFragment() {
+
+ private var _binding: FragmentProductOptionsBottomSheetBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = FragmentProductOptionsBottomSheetBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.btnEditProduct.setOnClickListener {
+ // Handle editing product
+ // Example: Open the edit activity or fragment
+ dismiss()
+ }
+
+ binding.btnDeleteProduct.setOnClickListener {
+ // Handle deleting product
+ // Example: Show confirmation dialog
+ dismiss()
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/StoreProductDetailActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/StoreProductDetailActivity.kt
index af53295..a22d850 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/StoreProductDetailActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/StoreProductDetailActivity.kt
@@ -1,11 +1,15 @@
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.text.Editable
-import android.text.TextWatcher
+import android.util.Log
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import com.alya.ecommerce_serang.R
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@@ -18,6 +22,11 @@ import com.alya.ecommerce_serang.databinding.ActivityStoreProductDetailBinding
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import java.io.File
+import java.io.FileOutputStream
import kotlin.getValue
class StoreProductDetailActivity : AppCompatActivity() {
@@ -25,6 +34,9 @@ class StoreProductDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityStoreProductDetailBinding
private lateinit var sessionManager: SessionManager
private lateinit var categoryList: List
+ private var imageUri: Uri? = null
+ private var sppirtUri: Uri? = null
+ private var halalUri: Uri? = null
private val viewModel: ProductViewModel by viewModels {
BaseViewModelFactory {
@@ -35,14 +47,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 = getFileName(uri)
+ binding.switcherSppirt.showNext()
+ }
+ }
+
+ private val halalLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
+ if (uri != null && isValidFile(uri)) {
+ halalUri = uri
+ binding.tvHalalName.text = getFileName(uri)
+ binding.switcherHalal.showNext()
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityStoreProductDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
setupHeader()
- observeCategories()
+
+ // Fetch categories
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
binding.switchIsPreOrder.setOnCheckedChangeListener { _, isChecked ->
@@ -50,126 +98,147 @@ class StoreProductDetailActivity : AppCompatActivity() {
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.btnRemoveFoto.setOnClickListener {
+ imageUri = null
+ binding.switcherFotoProduk.showPrevious()
+ validateForm()
+ }
+
+ 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()
+ }
+
validateForm()
binding.btnSaveProduct.setOnClickListener {
- if (binding.btnSaveProduct.isEnabled) addProduct()
+ if (!binding.btnSaveProduct.isEnabled) {
+ return@setOnClickListener
+ }
+ submitProduct()
+ }
+ }
+
+ private fun isValidFile(uri: Uri): Boolean {
+ val mimeType = contentResolver.getType(uri) ?: return false
+ return listOf("application/pdf", "image/jpeg", "image/png", "image/jpg").contains(mimeType)
+ }
+
+ private fun getFileName(uri: Uri): String {
+ return uri.lastPathSegment?.split("/")?.last() ?: "unknown_file"
+ }
+
+ private fun validateForm() {
+ 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 uriToNamedFile(uri: Uri, context: Context, prefix: String): File {
+ val extension = context.contentResolver.getType(uri)?.substringAfter("/") ?: "jpg"
+ val filename = "$prefix-${System.currentTimeMillis()}.$extension"
+ val file = File(context.cacheDir, filename)
+
+ context.contentResolver.openInputStream(uri)?.use { input ->
+ FileOutputStream(file).use { output -> input.copyTo(output) }
+ }
+
+ return file
+ }
+
+ private fun submitProduct() {
+ val name = binding.edtNamaProduk.text.toString()
+ val description = binding.edtDeskripsiProduk.text.toString()
+ val price = binding.edtHargaProduk.text.toString().toInt()
+ val stock = binding.edtStokProduk.text.toString().toInt()
+ val minOrder = binding.edtMinOrder.text.toString().toInt()
+ val weight = binding.edtBeratProduk.text.toString().toInt()
+ val isPreOrder = binding.switchIsPreOrder.isChecked
+ val duration = if (isPreOrder) binding.edtDurasi.text.toString().toInt() else 0
+ val status = if (binding.switchIsActive.isChecked) "active" else "inactive"
+ val categoryId = categoryList.getOrNull(binding.spinnerKategoriProduk.selectedItemPosition)?.id ?: 0
+
+ val imageFile = imageUri?.let { uriToNamedFile(it, this, "productimg") }
+ val sppirtFile = sppirtUri?.let { uriToNamedFile(it, this, "sppirt") }
+ val halalFile = halalUri?.let { uriToNamedFile(it, this, "halal") }
+
+ Log.d("File URI", "SPPIRT URI: ${sppirtUri.toString()}")
+ Log.d("File URI", "Halal URI: ${halalUri.toString()}")
+
+ val imagePart = imageFile?.let { createPartFromFile("productimg", it) }
+ val sppirtPart = sppirtFile?.let { createPartFromFile("sppirt", it) }
+ val halalPart = halalFile?.let { createPartFromFile("halal", it) }
+
+ viewModel.addProduct(
+ name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, status, imagePart, sppirtPart, halalPart
+ )
+
+ viewModel.productCreationResult.observe(this) { result ->
+ when (result) {
+ is Result.Loading -> binding.btnSaveProduct.isEnabled = false
+ is Result.Success -> {
+ val product = result.data.product
+ Toast.makeText(this, "Product Created: ${product?.productName}", Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ is Result.Error -> {
+ Log.e("ProductDetailActivity", "Error: ${result.exception.message}")
+ binding.btnSaveProduct.isEnabled = true
+ }
+ }
+ }
+ }
+
+ fun getMimeType(file: File): String {
+ val extension = file.extension
+ return when (extension.lowercase()) {
+ "jpg", "jpeg" -> "image/jpeg"
+ "png" -> "image/png"
+ "pdf" -> "application/pdf"
+ else -> "application/octet-stream"
+ }
+ }
+
+ fun createPartFromFile(field: String, file: File?): MultipartBody.Part? {
+ return file?.let {
+ val mimeType = getMimeType(it).toMediaTypeOrNull()
+ val requestBody = RequestBody.create(mimeType, it)
+ MultipartBody.Part.createFormData(field, it.name, requestBody)
}
}
private fun setupHeader() {
binding.header.headerTitle.text = "Tambah Produk"
-
- binding.header.headerLeftIcon.setOnClickListener {
- onBackPressedDispatcher.onBackPressed()
- }
- }
-
- private fun observeCategories() {
- viewModel.categoryList.observe(this) { result ->
- when (result) {
- is Result.Loading -> {
- // Optionally show loading spinner
- }
- 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) {
- 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 description = binding.edtDeskripsiProduk.text.toString()
- val price = binding.edtHargaProduk.text.toString().toIntOrNull() ?: 0
- val stock = binding.edtStokProduk.text.toString().toIntOrNull() ?: 0
- val minOrder = binding.edtMinOrder.text.toString().toIntOrNull() ?: 1
- val weight = binding.edtBeratProduk.text.toString().toIntOrNull() ?: 0
- val isPreOrder = binding.switchIsPreOrder.isChecked
- val duration = binding.edtDurasi.text.toString().toIntOrNull() ?: 0
- val isActive = binding.switchIsActive.isChecked
- val categoryPosition = binding.spinnerKategoriProduk.selectedItemPosition
- val categoryId = categoryList.getOrNull(categoryPosition)?.id ?: 0
-
- if (isPreOrder && duration == 0) {
- Toast.makeText(this, "Durasi wajib diisi jika pre-order diaktifkan.", Toast.LENGTH_SHORT).show()
- return
- }
-
- viewModel.addProduct(
- name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, isActive
- ).observe(this) { result ->
- when (result) {
- is Result.Loading -> binding.btnSaveProduct.isEnabled = false
- is Result.Success -> {
- Toast.makeText(this, "Produk berhasil ditambahkan!", Toast.LENGTH_SHORT).show()
- finish()
- }
- is Result.Error -> {
- binding.btnSaveProduct.isEnabled = true
- 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.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))
- }
+ binding.header.headerLeftIcon.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt
index d573936..f8f6d9c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt
@@ -3,19 +3,23 @@ package com.alya.ecommerce_serang.utils.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
+import com.alya.ecommerce_serang.data.api.response.product.CreateProductResponse
import com.alya.ecommerce_serang.data.api.response.product.Product
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.launch
+import okhttp3.MultipartBody
class ProductViewModel(private val repository: ProductRepository) : ViewModel() {
+ private val _productCreationResult = MutableLiveData>()
+ val productCreationResult: LiveData> get() = _productCreationResult
+
private val _productDetail = MutableLiveData()
val productDetail: LiveData get() = _productDetail
@@ -74,17 +78,20 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
isPreOrder: Boolean,
duration: Int,
categoryId: Int,
- isActive: Boolean
- ): LiveData> = liveData {
- emit(Result.Loading)
- val result = repository.addProduct(
- name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, isActive
- )
- emit(result)
+ status: String,
+ imagePart: MultipartBody.Part?,
+ sppirtPart: MultipartBody.Part?,
+ halalPart: MultipartBody.Part?
+ ) {
+ _productCreationResult.value = Result.Loading
+ viewModelScope.launch {
+ val result = repository.addProduct(
+ name, description, price, stock, minOrder, weight, isPreOrder, duration, categoryId, status, imagePart, sppirtPart, halalPart
+ )
+ _productCreationResult.value = result
+ }
}
-
-
// Optional: for store detail if you need it later
// fun loadStoreDetail(storeId: Int) {
// viewModelScope.launch {
@@ -92,4 +99,5 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
// _storeDetail.value = storeResult
// }
// }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_upload.xml b/app/src/main/res/drawable/bg_upload.xml
new file mode 100644
index 0000000..093fc1d
--- /dev/null
+++ b/app/src/main/res/drawable/bg_upload.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_close.png b/app/src/main/res/drawable/ic_close.png
new file mode 100644
index 0000000..72e947c
Binary files /dev/null and b/app/src/main/res/drawable/ic_close.png differ
diff --git a/app/src/main/res/drawable/ic_delete.png b/app/src/main/res/drawable/ic_delete.png
new file mode 100644
index 0000000..3b50ef4
Binary files /dev/null and b/app/src/main/res/drawable/ic_delete.png differ
diff --git a/app/src/main/res/drawable/ic_location.png b/app/src/main/res/drawable/ic_location.png
new file mode 100644
index 0000000..4e59b4b
Binary files /dev/null and b/app/src/main/res/drawable/ic_location.png differ
diff --git a/app/src/main/res/drawable/ic_person.png b/app/src/main/res/drawable/ic_person.png
new file mode 100644
index 0000000..ab1e6ef
Binary files /dev/null and b/app/src/main/res/drawable/ic_person.png differ
diff --git a/app/src/main/res/drawable/shape_sells_title.xml b/app/src/main/res/drawable/shape_sells_title.xml
new file mode 100644
index 0000000..939ef1f
--- /dev/null
+++ b/app/src/main/res/drawable/shape_sells_title.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_my_store.xml b/app/src/main/res/layout/activity_my_store.xml
index 1f6501a..5cf0ceb 100644
--- a/app/src/main/res/layout/activity_my_store.xml
+++ b/app/src/main/res/layout/activity_my_store.xml
@@ -75,7 +75,6 @@