diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9cb7bd8..ec2e571 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,9 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
+
@@ -69,11 +72,11 @@
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
+
-
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 a3a1358..2d08a55 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
@@ -288,6 +288,58 @@ class ProductRepository(private val apiService: ApiService) {
}
}
+ suspend fun getProductsByCategory(categoryId: Int): Result> =
+ withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Attempting to fetch products for category: $categoryId")
+ val response = apiService.getAllProduct()
+
+ if (response.isSuccessful) {
+ val allProducts = response.body()?.products ?: emptyList()
+
+ // Filter products by category_id
+ val filteredProducts = allProducts.filter { product ->
+ product.categoryId == categoryId
+ }
+
+ Log.d(TAG, "Filtered products for category $categoryId: ${filteredProducts.size} products")
+
+ Result.Success(filteredProducts)
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Failed to fetch products. Code: ${response.code()}, Error: $errorBody")
+ Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception while fetching products by category", e)
+ Result.Error(e)
+ }
+ }
+
+ // Optional: Get category by ID if needed
+ suspend fun getCategoryById(categoryId: Int): Result =
+ withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Attempting to fetch category: $categoryId")
+ val response = apiService.allCategory()
+
+ if (response.isSuccessful) {
+ val categories = response.body()?.category ?: emptyList()
+ val category = categories.find { it.id == categoryId }
+
+ Log.d(TAG, "Category found: ${category?.name}")
+ Result.Success(category)
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Failed to fetch category. Code: ${response.code()}, Error: $errorBody")
+ Result.Error(Exception("Failed to fetch category. Code: ${response.code()}"))
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception while fetching category by ID", e)
+ Result.Error(e)
+ }
+ }
+
companion object {
private const val TAG = "ProductRepository"
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt
index a902329..db00d1b 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt
@@ -24,6 +24,7 @@ import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
import com.alya.ecommerce_serang.ui.cart.CartActivity
import com.alya.ecommerce_serang.ui.notif.NotificationActivity
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
+import com.alya.ecommerce_serang.ui.product.category.CategoryProductsActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
import com.alya.ecommerce_serang.utils.SessionManager
@@ -211,7 +212,10 @@ class HomeFragment : Fragment() {
}
private fun handleCategoryProduct(category: CategoryItem) {
- // Your implementation
+ // Navigate to CategoryProductsActivity when category is clicked
+ val intent = Intent(requireContext(), CategoryProductsActivity::class.java)
+ intent.putExtra(CategoryProductsActivity.EXTRA_CATEGORY, category)
+ startActivity(intent)
}
override fun onDestroyView() {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt
index 7a95fdc..555976f 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt
@@ -42,19 +42,19 @@ class SearchResultsAdapter(
}
fun bind(product: ProductsItem) {
- binding.productName.text = product.name
- binding.productPrice.text = (product.price)
+ binding.tvProductName.text = product.name
+ binding.tvProductPrice.text = (product.price)
// Load image with Glide
Glide.with(binding.root.context)
.load(product.image)
.placeholder(R.drawable.placeholder_image)
// .error(R.drawable.error_image)
- .into(binding.productImage)
+ .into(binding.ivProductImage)
// Set store name if available
product.storeId?.toString().let {
- binding.storeName.text = it
+ binding.tvStoreName.text = it
}
}
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt
index 77d4e91..fc37df7 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt
@@ -2,11 +2,14 @@ package com.alya.ecommerce_serang.ui.order.detail
import android.Manifest
import android.R
+import android.app.Activity
import android.app.DatePickerDialog
+import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
+import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.webkit.MimeTypeMap
@@ -46,6 +49,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
private lateinit var productPrice: String
private var selectedImageUri: Uri? = null
+
private val viewModel: PaymentViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
@@ -62,55 +66,65 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
"Cash on Delivery"
)
- private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
- uri?.let {
- selectedImageUri = it
- binding.ivUploadedImage.setImageURI(selectedImageUri)
- binding.ivUploadedImage.visibility = View.VISIBLE
- binding.layoutUploadPlaceholder.visibility = View.GONE
+// private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
+// uri?.let {
+// selectedImageUri = it
+// binding.ivUploadedImage.setImageURI(selectedImageUri)
+// binding.ivUploadedImage.visibility = View.VISIBLE
+// binding.layoutUploadPlaceholder.visibility = View.GONE
+// }
+// }
+
+ private val pickImageLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ result.data?.data?.let { uri ->
+ handleSelectedImage(uri)
+ }
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityAddEvidencePaymentBinding.inflate(layoutInflater)
- setContentView(binding.root)
- sessionManager = SessionManager(this)
+ try {
+ binding = ActivityAddEvidencePaymentBinding.inflate(layoutInflater)
+ setContentView(binding.root)
- intent.extras?.let { bundle ->
- orderId = bundle.getInt("ORDER_ID", 0)
- paymentInfoId = bundle.getInt("PAYMENT_INFO_ID", 0)
- productPrice = intent.getStringExtra("TOTAL_AMOUNT") ?: "Rp0"
+ sessionManager = SessionManager(this)
+ intent.extras?.let { bundle ->
+ orderId = bundle.getInt("ORDER_ID", 0)
+ paymentInfoId = bundle.getInt("PAYMENT_INFO_ID", 0)
+ productPrice = intent.getStringExtra("TOTAL_AMOUNT") ?: "Rp0"
+ Log.d(TAG, "Intent data: OrderID=$orderId, PaymentInfoId=$paymentInfoId, Price=$productPrice")
+ }
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ enableEdgeToEdge()
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
+ val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ windowInsets
+ }
+
+ Log.d(TAG, "7. About to setup toolbar - COMMENTING OUT PROBLEMATIC LINE")
+ // COMMENT OUT THIS LINE TEMPORARILY:
+// binding.toolbar.navigationIcon.apply { finish() }
+
+ setupUI()
+
+ viewModel.getOrderDetails(orderId)
+
+ setupListeners()
+ setupObservers()
+
+ } catch (e: Exception) {
+ Log.e(TAG, "ERROR in AddEvidencePaymentActivity onCreate: ${e.message}", e)
+ Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
-
- WindowCompat.setDecorFitsSystemWindows(window, false)
-
- enableEdgeToEdge()
-
- // Apply insets to your root layout
- ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
- val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- view.setPadding(
- systemBars.left,
- systemBars.top,
- systemBars.right,
- systemBars.bottom
- )
- windowInsets
- }
-
- binding.toolbar.navigationIcon.apply {
- onBackPressed()
- }
-
- setupUI()
- viewModel.getOrderDetails(orderId)
-
-
- setupListeners()
- setupObservers()
}
private fun setupUI() {
@@ -126,11 +140,11 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
// Upload image button
binding.tvAddPhoto.setOnClickListener {
- checkPermissionAndPickImage()
+ checkPermissionsAndShowImagePicker()
}
binding.frameUploadImage.setOnClickListener {
- checkPermissionAndPickImage()
+ checkPermissionsAndShowImagePicker()
}
// Date picker
@@ -158,6 +172,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
// Submit button
binding.btnSubmit.setOnClickListener {
validateAndUpload()
+ Log.d(TAG, "AddEvidencePaymentActivity onCreate completed")
}
}
@@ -182,6 +197,112 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
}
}
+ private fun checkPermissionsAndShowImagePicker() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ // For Android 13+ (API 33+), use READ_MEDIA_IMAGES
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_CODE_STORAGE_PERMISSION)
+ } else {
+ showImagePickerOptions()
+ }
+ } else {
+ // For older versions, use READ_EXTERNAL_STORAGE
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE_STORAGE_PERMISSION)
+ } else {
+ showImagePickerOptions()
+ }
+ }
+ }
+
+ // Exact same approach as ChatActivity
+ private fun showImagePickerOptions() {
+ val options = arrayOf(
+ "Pilih dari Galeri",
+ "Batal"
+ )
+
+ androidx.appcompat.app.AlertDialog.Builder(this)
+ .setTitle("Pilih Bukti Pembayaran")
+ .setItems(options) { dialog, which ->
+ when (which) {
+ 0 -> openGallery() // Gallery
+ 1 -> dialog.dismiss() // Cancel
+ }
+ }
+ .show()
+ }
+
+ // Using the same gallery opening method as ChatActivity
+ private fun openGallery() {
+ try {
+ val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+ pickImageLauncher.launch(intent)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error opening gallery", e)
+ Toast.makeText(this, "Gagal membuka galeri", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun handleSelectedImage(uri: Uri) {
+ try {
+ Log.d(TAG, "Processing selected image: $uri")
+
+ // Use the same copy-to-cache approach as ChatActivity
+ contentResolver.openInputStream(uri)?.use { inputStream ->
+ val fileName = "evidence_${System.currentTimeMillis()}.jpg"
+ val outputFile = File(cacheDir, fileName)
+
+ outputFile.outputStream().use { outputStream ->
+ inputStream.copyTo(outputStream)
+ }
+
+ if (outputFile.exists() && outputFile.length() > 0) {
+ // Check file size (max 5MB like ChatActivity)
+ if (outputFile.length() > 5 * 1024 * 1024) {
+ Log.e(TAG, "File too large: ${outputFile.length()} bytes")
+ Toast.makeText(this, "Gambar terlalu besar (maksimal 5MB)", Toast.LENGTH_SHORT).show()
+ outputFile.delete()
+ return
+ }
+
+ // Success - update UI
+ selectedImageUri = Uri.fromFile(outputFile)
+ binding.ivUploadedImage.setImageURI(selectedImageUri)
+ binding.ivUploadedImage.visibility = View.VISIBLE
+ binding.layoutUploadPlaceholder.visibility = View.GONE
+
+ Log.d(TAG, "Image processed successfully: ${outputFile.absolutePath}, size: ${outputFile.length()}")
+ Toast.makeText(this, "Gambar berhasil dipilih", Toast.LENGTH_SHORT).show()
+ } else {
+ Log.e(TAG, "Failed to create image file")
+ Toast.makeText(this, "Gagal memproses gambar", Toast.LENGTH_SHORT).show()
+ }
+ } ?: run {
+ Log.e(TAG, "Could not open input stream for URI: $uri")
+ Toast.makeText(this, "Tidak dapat mengakses gambar", Toast.LENGTH_SHORT).show()
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error handling selected image", e)
+ Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ showImagePickerOptions()
+ } else {
+ Toast.makeText(this, "Izin diperlukan untuk mengakses galeri", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
private fun validateAndUpload() {
// Validate all fields
if (selectedImageUri == null) {
@@ -301,40 +422,6 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
}
-
-
- private fun checkPermissionAndPickImage() {
- val permission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
- Manifest.permission.READ_MEDIA_IMAGES
- } else {
- Manifest.permission.READ_EXTERNAL_STORAGE
- }
-
- if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_CODE_STORAGE_PERMISSION)
- } else {
- pickImage()
- }
- }
-
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == REQUEST_CODE_STORAGE_PERMISSION && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- pickImage()
- } else {
- Toast.makeText(this, "Izin dibutuhkan untuk memilih gambar", Toast.LENGTH_SHORT).show()
- }
- }
-
- private fun pickImage() {
- getContent.launch("image/*")
- }
-
-
private fun showDatePicker() {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt
index 72f824e..d2c525a 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt
@@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order.detail
import android.content.Intent
import android.os.Bundle
import android.util.Log
+import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
@@ -43,65 +44,88 @@ class PaymentActivity : AppCompatActivity() {
sessionManager = SessionManager(this)
- WindowCompat.setDecorFitsSystemWindows(window, false)
+ setupWindowInsets()
- enableEdgeToEdge()
-
- // Apply insets to your root layout
- ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
- val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- view.setPadding(
- systemBars.left,
- systemBars.top,
- systemBars.right,
- systemBars.bottom
- )
- windowInsets
- }
-
- // Mengambil data dari intent
+ // Get data from intent
val orderId = intent.getIntExtra("ORDER_ID", 0)
val paymentInfoId = intent.getIntExtra("ORDER_PAYMENT_ID", 0)
if (orderId == 0) {
Toast.makeText(this, "ID pesanan tidak valid", Toast.LENGTH_SHORT).show()
finish()
+ return
}
- // Setup toolbar
+ // Setup observers FIRST
+ observeData()
+
+ // Setup UI
+ setupToolbar()
+ setupClickListeners(orderId, paymentInfoId)
+
+ // Load data LAST
+ Log.d(TAG, "Fetching order details for Order ID: $orderId")
+ viewModel.getOrderDetails(orderId)
+ }
+
+ private fun setupWindowInsets() {
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ enableEdgeToEdge()
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
+ val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ windowInsets
+ }
+ }
+
+ private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener {
- onBackPressedDispatcher
finish()
}
+ }
- // Setup petunjuk transfer
+ private fun setupClickListeners(orderId: Int, paymentInfoId: Int) {
+ // Instructions clicks
binding.layoutMBankingInstructions.setOnClickListener {
- // Tampilkan instruksi mBanking
showInstructions("mBanking")
}
binding.layoutATMInstructions.setOnClickListener {
- // Tampilkan instruksi ATM
showInstructions("ATM")
}
- // Setup button upload bukti bayar
- binding.btnUploadPaymentProof.setOnClickListener {
-// Intent ke activity upload bukti bayar
- val intent = Intent(this, AddEvidencePaymentActivity::class.java)
- intent.putExtra("ORDER_ID", orderId)
- intent.putExtra("PAYMENT_INFO_ID", paymentInfoId)
- intent.putExtra("TOTAL_AMOUNT", binding.tvTotalAmount.text.toString())
- Log.d(TAG, "Received Order ID: $orderId, Payment Info ID: $paymentInfoId, Total Amount: ${binding.tvTotalAmount.text}")
+ // Upload button
+// binding.btnUploadPaymentProof.setOnClickListener { view ->
+// Log.d(TAG, "Button clicked - showing toast")
+// Toast.makeText(this@PaymentActivity, "Button works! OrderID: $orderId", Toast.LENGTH_LONG).show()
+// }
+ binding.btnUploadPaymentProof.apply {
+ isEnabled = true
+ isClickable = true
- startActivity(intent)
+ setOnClickListener {
+ Log.d(TAG, "Button clicked!")
+
+ val intent = Intent(this@PaymentActivity, AddEvidencePaymentActivity::class.java).apply {
+ putExtra("ORDER_ID", orderId)
+ putExtra("PAYMENT_INFO_ID", paymentInfoId)
+ putExtra("TOTAL_AMOUNT", binding.tvTotalAmount.text.toString())
+ }
+
+ Log.d(TAG, "Starting AddEvidencePaymentActivity with Order ID: $orderId, Payment Info ID: $paymentInfoId")
+ startActivity(intent)
+ }
+
+ // Debug touch events
+ setOnTouchListener { _, event ->
+ Log.d(TAG, "Button touched: ${event.action}")
+ false
+ }
}
- // Observe data
- observeData()
- // Load data
- Log.d(TAG, "Fetching order details for Order ID: $orderId")
- viewModel.getOrderDetails(orderId)
+ // Debug button state
+ Log.d(TAG, "Button setup - isEnabled: ${binding.btnUploadPaymentProof.isEnabled}, isClickable: ${binding.btnUploadPaymentProof.isClickable}")
}
private fun observeData() {
@@ -124,13 +148,14 @@ class PaymentActivity : AppCompatActivity() {
setupPaymentDueDate(order.updatedAt)
}
- // Observe loading state
viewModel.isLoading.observe(this) { isLoading ->
- // Show loading indicator if needed
- // binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ Log.d(TAG, "Loading state changed: $isLoading")
+ // Fix this line:
+ binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+
+ Log.d(TAG, "Button enabled: ${binding.btnUploadPaymentProof.isEnabled}")
}
- // Observe error
viewModel.error.observe(this) { error ->
if (error.isNotEmpty()) {
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/CategoryProductsActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/CategoryProductsActivity.kt
new file mode 100644
index 0000000..fb80bbb
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/CategoryProductsActivity.kt
@@ -0,0 +1,188 @@
+package com.alya.ecommerce_serang.ui.product.category
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.MenuItem
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.GridLayoutManager
+import com.alya.ecommerce_serang.BuildConfig.BASE_URL
+import com.alya.ecommerce_serang.R
+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.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.ProductRepository
+import com.alya.ecommerce_serang.databinding.ActivityCategoryProductsBinding
+import com.alya.ecommerce_serang.ui.product.DetailProductActivity
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.bumptech.glide.Glide
+import kotlinx.coroutines.launch
+
+class CategoryProductsActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityCategoryProductsBinding
+ private lateinit var sessionManager: SessionManager
+ private var productsAdapter: ProductsCategoryAdapter? = null
+
+ private val viewModel: CategoryProductsViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val productRepository = ProductRepository(apiService)
+ CategoryProductsViewModel(productRepository)
+ }
+ }
+
+ companion object {
+ const val EXTRA_CATEGORY = "extra_category"
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityCategoryProductsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ enableEdgeToEdge()
+
+ // Apply insets to your root layout
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
+ val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.setPadding(
+ systemBars.left,
+ systemBars.top,
+ systemBars.right,
+ systemBars.bottom
+ )
+ windowInsets
+ }
+
+ val category = intent.getParcelableExtra(EXTRA_CATEGORY)
+ if (category == null) {
+ finish()
+ return
+ }
+
+ setupUI(category)
+ setupRecyclerView()
+ observeViewModel()
+
+ // Load products for this category using category.id (not store_type_id)
+ viewModel.loadProductsByCategory(category.id)
+ }
+
+ private fun setupUI(category: CategoryItem) {
+ binding.apply {
+ // Setup toolbar
+ setSupportActionBar(toolbar)
+ supportActionBar?.apply {
+ setDisplayHomeAsUpEnabled(true)
+// title = category.name
+ }
+
+ val fullImageUrl = if (category.image.startsWith("/")) {
+ BASE_URL + category.image.removePrefix("/") // Append base URL if the path starts with "/"
+ } else {
+ category.image // Use as is if it's already a full URL
+ }
+
+ // Load category image
+ Glide.with(this@CategoryProductsActivity)
+ .load(fullImageUrl)
+ .placeholder(R.drawable.placeholder_image)
+ .error(R.drawable.placeholder_image)
+ .into(ivCategoryHeader)
+
+ tvCategoryTitle.text = category.name
+ }
+ }
+
+ private fun setupRecyclerView() {
+ productsAdapter = ProductsCategoryAdapter(
+ products = emptyList(),
+ onClick = { product -> handleProductClick(product) }
+ )
+
+ binding.rvProducts.apply {
+ layoutManager = GridLayoutManager(this@CategoryProductsActivity, 2)
+ adapter = productsAdapter
+// addItemDecoration(GridSpacingItemDecoration(2, 16, true))
+ }
+ }
+
+ private fun observeViewModel() {
+ lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.uiState.collect { state ->
+ when (state) {
+ is CategoryProductsUiState.Loading -> {
+ binding.progressBar.isVisible = true
+ binding.rvProducts.isVisible = false
+ binding.layoutError.isVisible = false
+ binding.layoutEmpty.isVisible = false
+ }
+ is CategoryProductsUiState.Success -> {
+ binding.progressBar.isVisible = false
+ binding.layoutError.isVisible = false
+
+ if (state.products.isEmpty()) {
+ binding.rvProducts.isVisible = false
+ binding.layoutEmpty.isVisible = true
+ } else {
+ binding.rvProducts.isVisible = true
+ binding.layoutEmpty.isVisible = false
+ productsAdapter?.updateProducts(state.products)
+ }
+ }
+ is CategoryProductsUiState.Error -> {
+ binding.progressBar.isVisible = false
+ binding.rvProducts.isVisible = false
+ binding.layoutEmpty.isVisible = false
+ binding.layoutError.isVisible = true
+ binding.tvErrorMessage.text = state.message
+
+ binding.btnRetry.setOnClickListener {
+ val category = intent.getParcelableExtra(
+ EXTRA_CATEGORY
+ )
+ category?.let { viewModel.loadProductsByCategory(it.id) }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun handleProductClick(product: ProductsItem) {
+ val intent = Intent(this, DetailProductActivity::class.java)
+ intent.putExtra("PRODUCT_ID", product.id)
+ startActivity(intent)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ android.R.id.home -> {
+ onBackPressed()
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ productsAdapter = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/CategoryProductsViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/CategoryProductsViewModel.kt
new file mode 100644
index 0000000..91c78a8
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/CategoryProductsViewModel.kt
@@ -0,0 +1,49 @@
+package com.alya.ecommerce_serang.ui.product.category
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.dto.ProductsItem
+import com.alya.ecommerce_serang.data.repository.ProductRepository
+import com.alya.ecommerce_serang.data.repository.Result
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+class CategoryProductsViewModel(
+ private val productRepository: ProductRepository
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(CategoryProductsUiState.Loading)
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun loadProductsByCategory(categoryId: Int) {
+ viewModelScope.launch {
+ _uiState.value = CategoryProductsUiState.Loading
+
+ when (val result = productRepository.getProductsByCategory(categoryId)) {
+ is com.alya.ecommerce_serang.data.repository.Result.Success -> {
+ _uiState.value = CategoryProductsUiState.Success(result.data)
+ }
+ is com.alya.ecommerce_serang.data.repository.Result.Error -> {
+ _uiState.value = CategoryProductsUiState.Error(
+ result.exception.message ?: "Failed to load products"
+ )
+ }
+ is Result.Loading -> {
+ // Handle if needed
+ }
+ }
+ }
+ }
+
+ fun retry(categoryId: Int) {
+ loadProductsByCategory(categoryId)
+ }
+}
+
+sealed class CategoryProductsUiState {
+ object Loading : CategoryProductsUiState()
+ data class Success(val products: List) : CategoryProductsUiState()
+ data class Error(val message: String) : CategoryProductsUiState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt
new file mode 100644
index 0000000..7e82b2b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt
@@ -0,0 +1,73 @@
+package com.alya.ecommerce_serang.ui.product.category
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.alya.ecommerce_serang.BuildConfig
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.ProductsItem
+import com.alya.ecommerce_serang.databinding.ItemProductGridBinding
+import com.bumptech.glide.Glide
+import java.text.NumberFormat
+import java.util.Locale
+
+class ProductsCategoryAdapter(
+ private var products: List,
+ private val onClick: (ProductsItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ fun updateProducts(newProducts: List) {
+ products = newProducts
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
+ val binding = ItemProductGridBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ return ProductViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
+ holder.bind(products[position])
+ }
+
+ override fun getItemCount(): Int = products.size
+
+ inner class ProductViewHolder(
+ private val binding: ItemProductGridBinding
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(product: ProductsItem) {
+ binding.apply {
+ tvProductName.text = product.name
+ val priceValue = product.price.toDoubleOrNull() ?: 0.0
+ tvProductPrice.text = "Rp ${NumberFormat.getNumberInstance(Locale("id", "ID")).format(priceValue.toInt())}"
+ // Load product image
+ Glide.with(itemView.context)
+ .load("${BuildConfig.BASE_URL}${product.image}")
+ .placeholder(R.drawable.placeholder_image)
+ .error(R.drawable.placeholder_image)
+ .centerCrop()
+ .into(ivProductImage)
+
+ // Set click listener
+ root.setOnClickListener {
+ onClick(product)
+ }
+
+ // Optional: Show stock status
+ if (product.stock > 0) {
+ tvStockStatus.text = "Stock: ${product.stock}"
+ tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.green))
+ } else {
+ tvStockStatus.text = "Out of Stock"
+ tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.red))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_category_products.xml b/app/src/main/res/layout/activity_category_products.xml
new file mode 100644
index 0000000..2c023dc
--- /dev/null
+++ b/app/src/main/res/layout/activity_category_products.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_payment.xml b/app/src/main/res/layout/activity_payment.xml
index c8d993d..3bccbf4 100644
--- a/app/src/main/res/layout/activity_payment.xml
+++ b/app/src/main/res/layout/activity_payment.xml
@@ -230,7 +230,20 @@
android:background="@drawable/bg_button_filled"
android:text="Kirim Bukti Bayar"
android:textAllCaps="false"
- android:textColor="@android:color/white" />
+ android:textColor="@android:color/white"
+ android:enabled="true"
+ android:clickable="true"
+ android:focusable="true" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_product_grid.xml b/app/src/main/res/layout/item_product_grid.xml
index cebc279..3d9c567 100644
--- a/app/src/main/res/layout/item_product_grid.xml
+++ b/app/src/main/res/layout/item_product_grid.xml
@@ -1,67 +1,84 @@
-
-
+ app:cardCornerRadius="12dp"
+ app:cardElevation="4dp"
+ android:foreground="?android:attr/selectableItemBackground">
-
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
-
+
+ android:orientation="vertical"
+ android:padding="12dp">
-
+
+
-
+
+
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c41219f..d7c0068 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -42,6 +42,8 @@
#7D8FAB
#489EC6
#faf069
+ #A5C882
+ #EC4E20
#489EC6
#8E8E8E