From 0b47d0beb8986b5d320be1cd1c61d1eafa65ce6f Mon Sep 17 00:00:00 2001
From: Gracia Hotmauli <95269134+hotmauligracia@users.noreply.github.com>
Date: Mon, 23 Jun 2025 11:11:03 +0700
Subject: [PATCH] store review
---
app/src/main/AndroidManifest.xml | 3 +
.../data/api/dto/ReviewsItem.kt | 30 +++++
.../store/review/ProductReviewResponse.kt | 13 ++
.../data/api/retrofit/ApiService.kt | 5 +
.../data/repository/ReviewRepository.kt | 50 +++++++
.../ui/profile/mystore/MyStoreActivity.kt | 21 +--
.../profile/mystore/review/ReviewActivity.kt | 74 +++++++++++
.../profile/mystore/review/ReviewAdapter.kt | 81 ++++++++++++
.../profile/mystore/review/ReviewFragment.kt | 52 ++++++--
.../mystore/review/ReviewListFragment.kt | 123 ++++++++++++++++++
.../mystore/review/ReviewViewPagerAdapter.kt | 24 ++++
.../utils/viewmodel/ReviewViewModel.kt | 68 +++++++++-
app/src/main/res/layout/activity_review.xml | 85 ++++++++++++
app/src/main/res/layout/fragment_review.xml | 29 ++++-
.../main/res/layout/fragment_review_list.xml | 41 ++++++
app/src/main/res/layout/fragment_sells.xml | 6 +-
.../main/res/layout/fragment_sells_list.xml | 10 +-
.../main/res/layout/item_store_product.xml | 8 +-
.../res/layout/item_store_product_review.xml | 105 +++++++++++++++
19 files changed, 783 insertions(+), 45 deletions(-)
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewsItem.kt
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/review/ProductReviewResponse.kt
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/repository/ReviewRepository.kt
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewActivity.kt
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewAdapter.kt
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewListFragment.kt
create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewViewPagerAdapter.kt
create mode 100644 app/src/main/res/layout/activity_review.xml
create mode 100644 app/src/main/res/layout/fragment_review_list.xml
create mode 100644 app/src/main/res/layout/item_store_product_review.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f7e9e77..ddaf412 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">
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewsItem.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewsItem.kt
new file mode 100644
index 0000000..44e3365
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewsItem.kt
@@ -0,0 +1,30 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class ReviewsItem(
+
+ @field:SerializedName("order_item_id")
+ val orderItemId: Int? = null,
+
+ @field:SerializedName("review_date")
+ val reviewDate: String? = null,
+
+ @field:SerializedName("user_image")
+ val userImage: String? = null,
+
+ @field:SerializedName("product_id")
+ val productId: Int? = null,
+
+ @field:SerializedName("rating")
+ val rating: Int? = null,
+
+ @field:SerializedName("review_text")
+ val reviewText: String? = null,
+
+ @field:SerializedName("product_name")
+ val productName: String? = null,
+
+ @field:SerializedName("username")
+ val username: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/review/ProductReviewResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/review/ProductReviewResponse.kt
new file mode 100644
index 0000000..3847b69
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/review/ProductReviewResponse.kt
@@ -0,0 +1,13 @@
+package com.alya.ecommerce_serang.data.api.response.store.review
+
+import com.alya.ecommerce_serang.data.api.dto.ReviewsItem
+import com.google.gson.annotations.SerializedName
+
+data class ProductReviewResponse(
+
+ @field:SerializedName("reviews")
+ val reviews: List? = null,
+
+ @field:SerializedName("message")
+ val message: String? = null
+)
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 a081e8e..1a04e32 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
@@ -75,6 +75,7 @@ import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductRe
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
import com.alya.ecommerce_serang.data.api.response.store.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
+import com.alya.ecommerce_serang.data.api.response.store.review.ProductReviewResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
import okhttp3.MultipartBody
@@ -507,4 +508,8 @@ interface ApiService {
@GET("mystore/notification")
suspend fun getNotifStore(
): Response
+
+ @GET("store/reviews")
+ suspend fun getStoreProductReview(
+ ): Response
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ReviewRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ReviewRepository.kt
new file mode 100644
index 0000000..b443a3b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ReviewRepository.kt
@@ -0,0 +1,50 @@
+package com.alya.ecommerce_serang.data.repository
+
+import com.alya.ecommerce_serang.data.api.dto.ProductsItem
+import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
+import com.alya.ecommerce_serang.data.api.response.store.review.ProductReviewResponse
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+
+class ReviewRepository(private val apiService: ApiService) {
+ suspend fun getReviewList(score: String): Result {
+ return try {
+ val response = apiService.getStoreProductReview()
+
+ if (response.isSuccessful) {
+ val allReviews = response.body()
+ val filteredReviews = if (score == "all") {
+ allReviews
+ } else {
+ val targetScore = score.toIntOrNull()
+ allReviews?.copy(reviews = allReviews.reviews?.filter {
+ val rating = it?.rating ?: 0
+ when(targetScore) {
+ 5 -> rating > 4
+ 4 -> rating > 3 && rating <= 4
+ 3 -> rating > 2 && rating <= 3
+ 2 -> rating > 1 && rating <= 2
+ 1 -> rating <= 1
+ else -> true
+ }
+ })
+ }
+ Result.Success(filteredReviews!!)
+ } else {
+ Result.Error(Exception("HTTP ${response.code()}: ${response.message()}"))
+ }
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
+ suspend fun getProductDetail(productId: Int): ProductResponse? {
+ return try {
+ val response = apiService.getDetailProduct(productId)
+ if (response.isSuccessful) {
+ response.body()
+ } else null
+ } catch (e: Exception) {
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt
index a1d78b1..12a8ad7 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt
@@ -18,6 +18,7 @@ import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceActivity
import com.alya.ecommerce_serang.ui.profile.mystore.chat.ChatListStoreActivity
import com.alya.ecommerce_serang.ui.profile.mystore.product.ProductActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.DetailStoreProfileActivity
+import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewActivity
import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
@@ -99,25 +100,21 @@ class MyStoreActivity : AppCompatActivity() {
}
binding.tvHistory.setOnClickListener {
- val intent = Intent(this, SellsActivity::class.java)
- startActivity(intent)
+ startActivity(Intent(this, SellsActivity::class.java))
}
binding.layoutPerluTagihan.setOnClickListener {
- val intent = Intent(this, SellsActivity::class.java)
- startActivity(intent)
+ startActivity(Intent(this, SellsActivity::class.java))
//navigateToSellsFragment("pending")
}
binding.layoutPembayaran.setOnClickListener {
- val intent = Intent(this, SellsActivity::class.java)
- startActivity(intent)
+ startActivity(Intent(this, SellsActivity::class.java))
//navigateToSellsFragment("paid")
}
binding.layoutPerluDikirim.setOnClickListener {
- val intent = Intent(this, SellsActivity::class.java)
- startActivity(intent)
+ startActivity(Intent(this, SellsActivity::class.java))
//navigateToSellsFragment("processed")
}
@@ -126,15 +123,11 @@ class MyStoreActivity : AppCompatActivity() {
}
binding.layoutReview.setOnClickListener {
- supportFragmentManager.beginTransaction()
- .replace(android.R.id.content, ReviewFragment())
- .addToBackStack(null)
- .commit()
+ startActivity(Intent(this, ReviewActivity::class.java))
}
binding.layoutInbox.setOnClickListener {
- val intent = Intent(this, ChatListStoreActivity::class.java)
- startActivity(intent)
+ startActivity(Intent(this, ChatListStoreActivity::class.java))
}
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewActivity.kt
new file mode 100644
index 0000000..8d131ea
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewActivity.kt
@@ -0,0 +1,74 @@
+package com.alya.ecommerce_serang.ui.profile.mystore.review
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.fragment.app.commit
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.ReviewRepository
+import com.alya.ecommerce_serang.databinding.ActivityReviewBinding
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.alya.ecommerce_serang.utils.viewmodel.ReviewViewModel
+
+class ReviewActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityReviewBinding
+ private lateinit var sessionManager: SessionManager
+
+ private val viewModel: ReviewViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val reviewRepository = ReviewRepository(apiService)
+ ReviewViewModel(reviewRepository)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityReviewBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
+ val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.setPadding(
+ systemBars.left,
+ systemBars.top,
+ systemBars.right,
+ systemBars.bottom
+ )
+ windowInsets
+ }
+
+ setupHeader()
+
+ viewModel.getReview("all")
+ viewModel.averageScore.observe(this) { binding.tvReviewScore.text = it }
+ viewModel.totalReview.observe(this) { binding.tvTotalReview.text = "$it rating" }
+ viewModel.totalReviewWithDesc.observe(this) { binding.tvTotalReviewWithDesc.text = "$it ulasan" }
+
+ if (savedInstanceState == null) {
+ showReviewFragment()
+ }
+ }
+
+ private fun setupHeader() {
+ binding.header.headerTitle.text = "Ulasan Pembeli"
+
+ binding.header.headerLeftIcon.setOnClickListener {
+ onBackPressed()
+ finish()
+ }
+ }
+
+ private fun showReviewFragment() {
+ supportFragmentManager.commit {
+ replace(R.id.fragment_container_reviews, ReviewFragment())
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewAdapter.kt
new file mode 100644
index 0000000..5eab57f
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewAdapter.kt
@@ -0,0 +1,81 @@
+package com.alya.ecommerce_serang.ui.profile.mystore.review
+
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.ReviewsItem
+import com.alya.ecommerce_serang.utils.viewmodel.ReviewViewModel
+import com.bumptech.glide.Glide
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class ReviewAdapter(
+ private val viewModel: ReviewViewModel
+): RecyclerView.Adapter() {
+
+ private val reviews = mutableListOf()
+ private var fragmentScore: String = "all"
+
+ fun setFragmentScore(score: String) {
+ fragmentScore = score
+ }
+
+ fun submitList(newReviews: List) {
+ reviews.clear()
+ reviews.addAll(newReviews)
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): ReviewAdapter.ReviewViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_store_product_review, parent, false)
+ return ReviewViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ReviewViewHolder, position: Int) {
+ if (position < reviews.size) {
+ holder.bind(reviews[position])
+ } else {
+ Log.e("ReviewAdapter", "Position $position is out of bounds for size ${reviews.size}")
+ }
+ }
+
+ override fun getItemCount(): Int = reviews.size
+
+ inner class ReviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val ivProduct: ImageView = itemView.findViewById(R.id.iv_product)
+ private val tvProductName: TextView = itemView.findViewById(R.id.tv_product_name)
+ private val tvReviewScore: TextView = itemView.findViewById(R.id.tv_review_score)
+ private val tvReviewDate: TextView = itemView.findViewById(R.id.tv_review_date)
+ private val tvUsername: TextView = itemView.findViewById(R.id.tv_username)
+ private val tvReviewDesc: TextView = itemView.findViewById(R.id.tv_review_desc)
+ private val ivMenu: ImageView = itemView.findViewById(R.id.iv_menu)
+
+ fun bind(review: ReviewsItem) {
+ val actualScore =
+ if (fragmentScore == "all") review.rating.toString() else fragmentScore
+
+ CoroutineScope(Dispatchers.Main).launch {
+ val imageUrl = viewModel.getProductImage(review.productId ?: -1)
+ Glide.with(itemView.context)
+ .load(imageUrl)
+ .placeholder(R.drawable.placeholder_image)
+ .into(ivProduct)
+ }
+
+ tvProductName.text = review.productName
+ tvReviewScore.text = actualScore
+ tvReviewDate.text = review.reviewDate
+ tvUsername.text = review.username
+ tvReviewDesc.text = review.reviewText
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewFragment.kt
index 45e553b..0b9e947 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewFragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewFragment.kt
@@ -1,32 +1,56 @@
package com.alya.ecommerce_serang.ui.profile.mystore.review
-import androidx.fragment.app.viewModels
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.utils.viewmodel.ReviewViewModel
+import com.alya.ecommerce_serang.databinding.FragmentReviewBinding
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.google.android.material.tabs.TabLayoutMediator
class ReviewFragment : Fragment() {
- companion object {
- fun newInstance() = ReviewFragment()
- }
+ private var _binding: FragmentReviewBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var sessionManager: SessionManager
- private val viewModel: ReviewViewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- // TODO: Use the ViewModel
- }
+ private lateinit var viewPagerAdapter: ReviewViewPagerAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- return inflater.inflate(R.layout.fragment_review, container, false)
+ _binding = FragmentReviewBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ sessionManager = SessionManager(requireContext())
+
+ setupViewPager()
+ }
+
+ private fun setupViewPager() {
+ viewPagerAdapter = ReviewViewPagerAdapter(requireActivity())
+ binding.viewPagerReview.adapter = viewPagerAdapter
+
+ TabLayoutMediator(binding.tabLayoutReview, binding.viewPagerReview) { tab, position ->
+ tab.text = when (position) {
+ 0 -> "Semua"
+ 1 -> "5 Bintang"
+ 2 -> "4 Bintang"
+ 3 -> "3 Bintang"
+ 4 -> "2 Bintang"
+ 5 -> "1 Bintang"
+ else -> "Tab $position"
+ }
+ }.attach()
+ }
+
+ 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/review/ReviewListFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewListFragment.kt
new file mode 100644
index 0000000..029d873
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewListFragment.kt
@@ -0,0 +1,123 @@
+package com.alya.ecommerce_serang.ui.profile.mystore.review
+
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.ReviewRepository
+import com.alya.ecommerce_serang.databinding.FragmentReviewListBinding
+import com.alya.ecommerce_serang.ui.order.address.ViewState
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
+import com.alya.ecommerce_serang.utils.viewmodel.ReviewViewModel
+
+class ReviewListFragment : Fragment() {
+
+ private var _binding: FragmentReviewListBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var sessionManager: SessionManager
+
+ private lateinit var reviewAdapter: ReviewAdapter
+ private val viewModel: ReviewViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(SessionManager(requireContext()))
+ ReviewViewModel(ReviewRepository(apiService))
+ }
+ }
+
+ private var score: String = "all"
+
+ companion object {
+ private const val ARG_SCORE = "score"
+
+ fun newInstance(score: String): ReviewListFragment = ReviewListFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_SCORE, score)
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ sessionManager = SessionManager(requireContext())
+ score = arguments?.getString(ARG_SCORE) ?: "all"
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = FragmentReviewListBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ reviewAdapter = ReviewAdapter(viewModel)
+ binding.rvReview.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = reviewAdapter
+ }
+
+ observeReviewList()
+ fetchReviewByScore(score)
+ }
+
+ private fun fetchReviewByScore(score: String) {
+ val normalizedScore = when (score) {
+ "all" -> "all"
+ else -> {
+ val scoreValue = score.toDoubleOrNull() ?: 0.0
+ when {
+ scoreValue > 4.5 -> "5"
+ scoreValue > 3.5 -> "4"
+ scoreValue > 2.5 -> "3"
+ scoreValue > 1.5 -> "2"
+ else -> "1"
+ }
+ }
+ }
+ viewModel.getReview(normalizedScore)
+ }
+
+ private fun observeReviewList() {
+ viewModel.review.observe(viewLifecycleOwner) { result ->
+ when (result) {
+ is ViewState.Success -> {
+ val data = result.data.orEmpty().sortedByDescending { it.reviewDate }
+ binding.progressBar.visibility = View.GONE
+
+ if (data.isEmpty()) {
+ binding.tvEmptyState.visibility = View.VISIBLE
+ binding.rvReview.visibility = View.GONE
+ } else {
+ binding.tvEmptyState.visibility = View.GONE
+ binding.rvReview.visibility = View.VISIBLE
+ reviewAdapter.submitList(data)
+ }
+ }
+
+ is ViewState.Loading -> binding.progressBar.visibility = View.VISIBLE
+ is ViewState.Error -> {
+ binding.progressBar.visibility = View.GONE
+ binding.tvEmptyState.visibility = View.VISIBLE
+ Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ 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/review/ReviewViewPagerAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewViewPagerAdapter.kt
new file mode 100644
index 0000000..8973690
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/review/ReviewViewPagerAdapter.kt
@@ -0,0 +1,24 @@
+package com.alya.ecommerce_serang.ui.profile.mystore.review
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.viewpager2.adapter.FragmentStateAdapter
+
+class ReviewViewPagerAdapter(
+ fragmentActivity: FragmentActivity
+) : FragmentStateAdapter(fragmentActivity) {
+ private val reviewScore = listOf(
+ "all",
+ "5",
+ "4",
+ "3",
+ "2",
+ "1"
+ )
+
+ override fun getItemCount(): Int = reviewScore.size
+
+ override fun createFragment(position: Int): Fragment {
+ return ReviewListFragment.newInstance(reviewScore[position])
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ReviewViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ReviewViewModel.kt
index 3959502..2493692 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ReviewViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ReviewViewModel.kt
@@ -1,7 +1,71 @@
package com.alya.ecommerce_serang.utils.viewmodel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.BuildConfig.BASE_URL
+import com.alya.ecommerce_serang.data.api.dto.ReviewsItem
+import com.alya.ecommerce_serang.data.repository.Result
+import com.alya.ecommerce_serang.data.repository.ReviewRepository
+import com.alya.ecommerce_serang.ui.order.address.ViewState
+import kotlinx.coroutines.launch
+import kotlin.getOrThrow
-class ReviewViewModel : ViewModel() {
- // TODO: Implement the ViewModel
+class ReviewViewModel(private val repository: ReviewRepository) : ViewModel() {
+
+ private val _review = MutableLiveData>>()
+ val review: LiveData>> = _review
+
+ private val _averageScore = MutableLiveData()
+ val averageScore: LiveData = _averageScore
+
+ private val _totalReview = MutableLiveData()
+ val totalReview: LiveData = _totalReview
+
+ private val _totalReviewWithDesc = MutableLiveData()
+ val totalReviewWithDesc: LiveData = _totalReviewWithDesc
+
+ private val _isLoading = MutableLiveData()
+ val isLoading: LiveData = _isLoading
+
+ private val productImageCache = mutableMapOf()
+
+ fun getReview(score: String) {
+ _review.value = ViewState.Loading
+ viewModelScope.launch {
+ try {
+ val response = repository.getReviewList(score)
+ if (response is Result.Success) {
+ val reviews = response.data.reviews?.filterNotNull().orEmpty()
+ _review.value = ViewState.Success(reviews)
+
+ if (score == "all") {
+ val avg = if (reviews.isNotEmpty()) {
+ reviews.mapNotNull { it.rating }.average()
+ } else 0.0
+ _averageScore.value = String.format("%.1f", avg)
+ _totalReview.value = reviews.size
+ _totalReviewWithDesc.value = reviews.count { !it.reviewText.isNullOrBlank() }
+ }
+ } else if (response is Result.Error) {
+ _review.value = ViewState.Error(response.exception.message ?: "Gagal memuat ulasan")
+ }
+ } catch (e: Exception) {
+ _review.value = ViewState.Error(e.message ?: "Terjadi kesalahan")
+ }
+ }
+ }
+
+ suspend fun getProductImage(productId: Int): String? {
+ if (productImageCache.containsKey(productId)) {
+ return productImageCache[productId]
+ }
+ val result = repository.getProductDetail(productId)
+ val imageUrl = if (result?.product?.image?.startsWith("/") == true) {
+ BASE_URL + result.product.image.removePrefix("/")
+ } else result?.product?.image
+ productImageCache[productId] = imageUrl.toString()
+ return imageUrl.toString()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_review.xml b/app/src/main/res/layout/activity_review.xml
new file mode 100644
index 0000000..40b70c0
--- /dev/null
+++ b/app/src/main/res/layout/activity_review.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_review.xml b/app/src/main/res/layout/fragment_review.xml
index dace922..84f61b0 100644
--- a/app/src/main/res/layout/fragment_review.xml
+++ b/app/src/main/res/layout/fragment_review.xml
@@ -1,13 +1,32 @@
-
-
+ android:layout_height="wrap_content"
+ app:tabMode="scrollable"
+ app:tabTextAppearance="@style/label_medium_prominent"
+ app:tabSelectedTextAppearance="@style/label_medium_prominent"
+ app:tabIndicatorColor="@color/blue_500"
+ app:tabSelectedTextColor="@color/blue_500"
+ app:tabTextColor="@color/black_300"
+ app:tabBackground="@color/white"
+ app:tabPadding="13dp"
+ app:layout_constraintTop_toTopOf="parent"/>
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_review_list.xml b/app/src/main/res/layout/fragment_review_list.xml
new file mode 100644
index 0000000..19438d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_review_list.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_sells.xml b/app/src/main/res/layout/fragment_sells.xml
index 70e8e3f..2d20d1e 100644
--- a/app/src/main/res/layout/fragment_sells.xml
+++ b/app/src/main/res/layout/fragment_sells.xml
@@ -9,7 +9,7 @@
tools:context=".ui.profile.mystore.sells.SellsFragment">
+ app:layout_constraintTop_toBottomOf="@+id/tab_layout_sells" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_sells_list.xml b/app/src/main/res/layout/fragment_sells_list.xml
index c74d548..8dd8bae 100644
--- a/app/src/main/res/layout/fragment_sells_list.xml
+++ b/app/src/main/res/layout/fragment_sells_list.xml
@@ -7,7 +7,7 @@
tools:context=".ui.profile.mystore.sells.SellsListFragment">
+ android:orientation="vertical"
+ android:clickable="true"
+ android:focusable="true">
+ android:layout_marginStart="8dp"
+ android:clickable="true"
+ android:focusable="true"/>
diff --git a/app/src/main/res/layout/item_store_product_review.xml b/app/src/main/res/layout/item_store_product_review.xml
new file mode 100644
index 0000000..43b14c6
--- /dev/null
+++ b/app/src/main/res/layout/item_store_product_review.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file