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