diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 50af9ab..6693f7e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,7 +16,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
- tools:targetApi="31" >
+ tools:targetApi="31">
+
@@ -58,8 +61,7 @@
android:exported="false" />
-
+ android:exported="false">
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt
index d7313b5..f9a927c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt
@@ -20,7 +20,7 @@ data class ReviewsItem(
val reviewDate: String,
@field:SerializedName("user_image")
- val userImage: Any,
+ val userImage: String? = null,
@field:SerializedName("product_id")
val productId: Int,
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 c9b951a..5cf478a 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
@@ -5,6 +5,7 @@ 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.response.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.CategoryResponse
+import com.alya.ecommerce_serang.data.api.response.DetailStoreProductResponse
import com.alya.ecommerce_serang.data.api.response.LoginResponse
import com.alya.ecommerce_serang.data.api.response.OtpResponse
import com.alya.ecommerce_serang.data.api.response.ProductResponse
@@ -55,6 +56,11 @@ interface ApiService {
@GET("profile")
suspend fun getUserProfile(): Response
+ @GET("store/detail/{id}")
+ suspend fun getDetailStore (
+ @Path("id") storeId: Int
+ ): Response
+
@GET("mystore")
fun getStore (): Call
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 2c82c17..519b5d4 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
@@ -23,6 +23,7 @@ class ProductRepository(private val apiService: ApiService) {
} else {
// Return a Result.Error with a custom Exception
+ Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
}
} catch (e: Exception) {
@@ -78,5 +79,19 @@ class ProductRepository(private val apiService: ApiService) {
null
}
}
+}
-}
\ No newline at end of file
+// suspend fun fetchStoreDetail(storeId: Int): Store? {
+// return try {
+// val response = apiService.getStore(storeId)
+// if (response.isSucessful) {
+// response.body()?.store
+// } else {
+// Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
+//
+// null
+// }
+// } catch (e: Exception) {
+// null
+// }
+// }
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt
index b55ed61..e8de51a 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt
@@ -1,16 +1,21 @@
package com.alya.ecommerce_serang.ui.product
+import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.Product
+import com.alya.ecommerce_serang.data.api.response.ReviewsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
+import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.bumptech.glide.Glide
@@ -19,6 +24,8 @@ class DetailProductActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailProductBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
+ private var productAdapter: HorizontalProductAdapter? = null
+ private var reviewsAdapter: ReviewsAdapter? = null
private val viewModel: ProductViewModel by viewModels {
BaseViewModelFactory {
@@ -36,19 +43,15 @@ class DetailProductActivity : AppCompatActivity() {
apiService = ApiConfig.getApiService(sessionManager)
val productId = intent.getIntExtra("PRODUCT_ID", -1)
+ //nanti tambah get store id dari HomeFragment Product.storeId
if (productId == -1) {
Log.e("DetailProductActivity", "Invalid Product ID")
finish() // Close activity if no valid ID
return
}
-// ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
-// val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
-// v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
-// insets
-// }
-
viewModel.loadProductDetail(productId)
+ viewModel.loadReviews(productId)
viewModel.productDetail.observe(this) { product ->
if (product != null) {
@@ -61,6 +64,7 @@ class DetailProductActivity : AppCompatActivity() {
}
}
observeProductDetail()
+ observeProductReviews()
}
private fun observeProductDetail() {
viewModel.productDetail.observe(this) { product ->
@@ -68,6 +72,12 @@ class DetailProductActivity : AppCompatActivity() {
}
}
+ private fun observeProductReviews() {
+ viewModel.reviewProduct.observe(this) { reviews ->
+ setupRecyclerViewReviewsProduct(reviews)
+ }
+ }
+
private fun updateUI(product: Product){
binding.tvProductName.text = product.productName
binding.tvPrice.text = product.price
@@ -79,6 +89,10 @@ class DetailProductActivity : AppCompatActivity() {
binding.tvDescription.text = product.description
binding.tvSellerName.text = product.storeId.toString()
+ binding.tvViewAllReviews.setOnClickListener{
+ handleAllReviewsClick(product.productId)
+ }
+
val fullImageUrl = when (val img = product.image) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
@@ -91,5 +105,52 @@ class DetailProductActivity : AppCompatActivity() {
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(binding.ivProductImage)
+
+ setupRecyclerViewOtherProducts()
+ }
+
+ private fun handleAllReviewsClick(productId: Int) {
+ val intent = Intent(this, ReviewProductActivity::class.java)
+ intent.putExtra("PRODUCT_ID", productId) // Pass product ID
+ startActivity(intent)
+ }
+
+ private fun setupRecyclerViewOtherProducts(){
+ productAdapter = HorizontalProductAdapter(
+ products = emptyList(),
+ onClick = { productsItem -> handleProductClick(productsItem) }
+ )
+
+ binding.recyclerViewOtherProducts.apply {
+ adapter = productAdapter
+ layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.HORIZONTAL,
+ false
+ )
+ }
+ }
+
+ private fun setupRecyclerViewReviewsProduct(reviewList: List){
+ val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
+
+ reviewsAdapter = ReviewsAdapter(
+ reviewList = limitedReviewList
+ )
+
+ binding.recyclerViewReviews.apply {
+ adapter = reviewsAdapter
+ layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ }
+ }
+
+ private fun handleProductClick(product: ProductsItem) {
+ val intent = Intent(this, DetailProductActivity::class.java)
+ intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
+ startActivity(intent)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductViewModel.kt
index f822239..87bb380 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductViewModel.kt
@@ -5,6 +5,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.Product
+import com.alya.ecommerce_serang.data.api.response.ReviewsItem
+import com.alya.ecommerce_serang.data.api.response.Store
import com.alya.ecommerce_serang.data.repository.ProductRepository
import kotlinx.coroutines.launch
@@ -13,10 +15,30 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
private val _productDetail = MutableLiveData()
val productDetail: LiveData get() = _productDetail
+ private val _storeDetail = MutableLiveData()
+ val storeDetail : LiveData get() = _storeDetail
+
+ private val _reviewProduct = MutableLiveData>()
+ val reviewProduct: LiveData> get() = _reviewProduct
+
fun loadProductDetail(productId: Int) {
viewModelScope.launch {
val result = repository.fetchProductDetail(productId)
_productDetail.value = result?.product
}
}
-}
\ No newline at end of file
+
+ fun loadReviews(productId: Int) {
+ viewModelScope.launch {
+ val reviews = repository.fetchProductReview(productId)
+ _reviewProduct.value = reviews ?: emptyList()
+ }
+ }
+}
+
+// fun loadStoreDetail(storeId: Int){
+// viewModelScope.launch {
+// val storeResult = repository.fetchStoreDetail(storeId)
+// _storeDetail.value = storeResult
+// }
+// }
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/ReviewProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ReviewProductActivity.kt
new file mode 100644
index 0000000..ebcb6a4
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ReviewProductActivity.kt
@@ -0,0 +1,79 @@
+package com.alya.ecommerce_serang.ui.product
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.alya.ecommerce_serang.data.repository.ProductRepository
+import com.alya.ecommerce_serang.databinding.ActivityReviewProductBinding
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+
+class ReviewProductActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityReviewProductBinding
+ private lateinit var apiService: ApiService
+ private var reviewsAdapter: ReviewsAdapter? = null
+ private lateinit var sessionManager: SessionManager
+ private val viewModel: ProductViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val productRepository = ProductRepository(apiService)
+ ProductViewModel(productRepository)
+ }
+ }
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityReviewProductBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ enableEdgeToEdge()
+
+ sessionManager = SessionManager(this)
+ apiService = ApiConfig.getApiService(sessionManager)
+
+ val productId = intent.getIntExtra("PRODUCT_ID", -1) // Get the product ID
+ if (productId == -1) {
+ Log.e("ReviewProductActivity", "Invalid Product ID")
+ finish() // Close the activity if the product ID is invalid
+ return
+ }
+
+ setupRecyclerView()
+ viewModel.loadReviews(productId) // Fetch reviews using productId
+
+ observeReviews() // Observe review data
+ }
+
+ private fun observeReviews() {
+ viewModel.reviewProduct.observe(this) { reviews ->
+ if (reviews.isNotEmpty()) {
+ reviewsAdapter?.setReviews(reviews)
+ binding.tvNoReviews.visibility = View.GONE
+ } else {
+ binding.tvNoReviews.visibility = View.VISIBLE // Show "No Reviews" message
+ }
+ }
+ }
+
+ private fun setupRecyclerView() {
+ reviewsAdapter = ReviewsAdapter(
+ reviewList = emptyList()
+ )
+
+ binding.rvReviewsProduct.apply {
+ adapter = reviewsAdapter
+ layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/ReviewsAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ReviewsAdapter.kt
new file mode 100644
index 0000000..6588d28
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ReviewsAdapter.kt
@@ -0,0 +1,61 @@
+package com.alya.ecommerce_serang.ui.product
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.response.ReviewsItem
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+class ReviewsAdapter(
+ private var reviewList: List
+) : RecyclerView.Adapter() {
+
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReviewViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_review, parent, false)
+ return ReviewViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ReviewViewHolder, position: Int) {
+ val review = reviewList[position]
+
+ with(holder) {
+ tvReviewerName.text = review.username
+ tvReviewRating.text = review.rating.toString()
+ tvReviewText.text = review.reviewText
+ tvDateReview.text = formatDate(review.reviewDate)
+ }
+ }
+
+ override fun getItemCount(): Int = reviewList.size
+
+ fun setReviews(reviews: List) {
+ reviewList = reviews
+ notifyDataSetChanged()
+ }
+
+ private fun formatDate(dateString: String): String {
+ return try {
+ val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) //from json
+ inputFormat.timeZone = TimeZone.getTimeZone("UTC") //get timezone
+ val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) // new format
+ val date = inputFormat.parse(dateString) // Parse from json format
+ outputFormat.format(date!!) // convert to new format
+ } catch (e: Exception) {
+ dateString // Return original if error occurs
+ }
+ }
+
+ class ReviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val tvReviewRating: TextView = itemView.findViewById(R.id.tvReviewRating)
+ val tvReviewerName: TextView = itemView.findViewById(R.id.tvUsername)
+ val tvReviewText: TextView = itemView.findViewById(R.id.tvReviewText)
+ val tvDateReview: TextView = itemView.findViewById(R.id.date_review)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_detail_profile.xml b/app/src/main/res/layout/activity_detail_profile.xml
index 389195b..55d422d 100644
--- a/app/src/main/res/layout/activity_detail_profile.xml
+++ b/app/src/main/res/layout/activity_detail_profile.xml
@@ -15,6 +15,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
+ android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
diff --git a/app/src/main/res/layout/activity_review_product.xml b/app/src/main/res/layout/activity_review_product.xml
new file mode 100644
index 0000000..8203d26
--- /dev/null
+++ b/app/src/main/res/layout/activity_review_product.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_review.xml b/app/src/main/res/layout/item_review.xml
index 0b1ad13..a59ed37 100644
--- a/app/src/main/res/layout/item_review.xml
+++ b/app/src/main/res/layout/item_review.xml
@@ -50,6 +50,14 @@
android:textColor="@color/black" />
+