mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 17:32:22 +00:00
add reviews
This commit is contained in:
@ -16,7 +16,10 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Ecommerce_serang"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31" >
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.product.ReviewProductActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.profile.mystore.balance.BalanceActivity"
|
||||
android:exported="false" />
|
||||
@ -58,8 +61,7 @@
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
android:exported="false"></activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -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,
|
||||
|
@ -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<ProfileResponse>
|
||||
|
||||
@GET("store/detail/{id}")
|
||||
suspend fun getDetailStore (
|
||||
@Path("id") storeId: Int
|
||||
): Response<DetailStoreProductResponse>
|
||||
|
||||
|
||||
@GET("mystore")
|
||||
fun getStore (): Call<StoreResponse>
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 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
|
||||
// }
|
||||
// }
|
@ -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<ReviewsItem>){
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<Product?>()
|
||||
val productDetail: LiveData<Product?> get() = _productDetail
|
||||
|
||||
private val _storeDetail = MutableLiveData<Store?>()
|
||||
val storeDetail : LiveData<Store?> get() = _storeDetail
|
||||
|
||||
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
|
||||
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
|
||||
|
||||
fun loadProductDetail(productId: Int) {
|
||||
viewModelScope.launch {
|
||||
val result = repository.fetchProductDetail(productId)
|
||||
_productDetail.value = result?.product
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// }
|
||||
// }
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ReviewsItem>
|
||||
) : RecyclerView.Adapter<ReviewsAdapter.ReviewViewHolder>() {
|
||||
|
||||
|
||||
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<ReviewsItem>) {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
63
app/src/main/res/layout/activity_review_product.xml
Normal file
63
app/src/main/res/layout/activity_review_product.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.product.ReviewProductActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/topAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar"
|
||||
android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_back_24"
|
||||
android:contentDescription="back"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_review_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ulasan Produk"
|
||||
android:textSize="20sp"
|
||||
android:fontFamily="@font/dmsans_bold"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"/>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_reviews_product"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintTop_toBottomOf="@id/topAppBar"
|
||||
tools:itemCount="5"
|
||||
tools:listitem="@layout/item_review" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_no_reviews"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="Tidak Ada Ulasan"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_reviews_product"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -50,6 +50,14 @@
|
||||
android:textColor="@color/black" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date_review"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/dmsans_light"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/soft_gray"
|
||||
android:text="22-03-2025"/>
|
||||
<!-- Review Text -->
|
||||
<TextView
|
||||
android:id="@+id/tvReviewText"
|
||||
|
Reference in New Issue
Block a user