From edddecaaf0dfe714dee6608d58ef7b23c954ba48 Mon Sep 17 00:00:00 2001 From: shaulascr Date: Tue, 13 May 2025 11:07:30 +0700 Subject: [PATCH] update add review, fix cart, fix order --- app/src/main/AndroidManifest.xml | 3 + .../data/api/dto/ReviewProductItem.kt | 10 +- .../product/DetailStoreProductResponse.kt | 48 +++-- .../data/repository/OrderRepository.kt | 49 ++++- .../ui/order/CheckoutActivity.kt | 17 +- .../ui/order/CheckoutViewModel.kt | 75 ++++--- .../ui/order/PaymentMethodAdapter.kt | 14 +- .../ui/order/history/HistoryViewModel.kt | 32 +++ .../ui/order/history/OrderHistoryAdapter.kt | 73 ++++++- .../ui/order/review/AddReviewAdapter.kt | 64 ++++++ .../ui/order/review/CreateReviewActivity.kt | 188 ++++++++++++++++++ .../ui/order/review/CreateReviewViewModel.kt | 41 ++++ .../res/layout/activity_create_review.xml | 72 +++++++ .../main/res/layout/item_review_product.xml | 84 ++++++++ app/src/main/res/values/colors.xml | 1 + 15 files changed, 687 insertions(+), 84 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/order/review/AddReviewAdapter.kt create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewActivity.kt create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewViewModel.kt create mode 100644 app/src/main/res/layout/activity_create_review.xml create mode 100644 app/src/main/res/layout/item_review_product.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cab34b5..3c3dfbd 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/ReviewProductItem.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewProductItem.kt index cfe8a65..9d9da34 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewProductItem.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ReviewProductItem.kt @@ -10,5 +10,13 @@ data class ReviewProductItem ( val rating : Int, @SerializedName("review_text") - val reviewTxt : String + val reviewTxt : String = "" +) + +data class ReviewUIItem( + val orderItemId: Int, + val productName: String, + val productImage: String, + var rating: Int = 5, // Default rating is 5 stars + var reviewText: String = "" // Empty by default, to be filled by user ) \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/DetailStoreProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/DetailStoreProductResponse.kt index fa02d06..0e2015f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/DetailStoreProductResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/product/DetailStoreProductResponse.kt @@ -4,6 +4,12 @@ import com.google.gson.annotations.SerializedName data class DetailStoreProductResponse( + @field:SerializedName("shipping") + val shipping: List, + + @field:SerializedName("payment") + val payment: List, + @field:SerializedName("store") val store: StoreProduct, @@ -11,28 +17,11 @@ data class DetailStoreProductResponse( val message: String ) -data class PaymentInfoItem( - - val id: Int = 1, - - @field:SerializedName("qris_image") - val qrisImage: String? = null, - - @field:SerializedName("bank_num") - val bankNum: String, - - @field:SerializedName("name") - val name: String -) - data class StoreProduct( @field:SerializedName("store_id") val storeId: Int, - @field:SerializedName("shipping_service") - val shippingService: List, - @field:SerializedName("store_rating") val storeRating: String, @@ -45,21 +34,36 @@ data class StoreProduct( @field:SerializedName("store_type") val storeType: String, - @field:SerializedName("payment_info") - val paymentInfo: List, - @field:SerializedName("store_location") val storeLocation: String, @field:SerializedName("store_image") - val storeImage: String, + val storeImage: String? = null, @field:SerializedName("status") val status: String ) -data class ShippingServiceItem( +data class ShippingItemDetail( @field:SerializedName("courier") val courier: String ) + +data class PaymentItemDetail( + + @field:SerializedName("qris_image") + val qrisImage: String, + + @field:SerializedName("bank_num") + val bankNum: String, + + @field:SerializedName("account_name") + val accountName: Any, + + @field:SerializedName("bank_name") + val bankName: String, + + @field:SerializedName("id") + val id: Int +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt index 5af3dac..835035c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt @@ -8,15 +8,18 @@ import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy import com.alya.ecommerce_serang.data.api.dto.OrdersItem +import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem import com.alya.ecommerce_serang.data.api.dto.UpdateCart import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse +import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse +import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentItemDetail import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse @@ -179,8 +182,6 @@ class OrderRepository(private val apiService: ApiService) { } } - - suspend fun updateCart(updateCart: UpdateCart): Result { return try { val response = apiService.updateCart(updateCart) @@ -236,6 +237,27 @@ class OrderRepository(private val apiService: ApiService) { } } + suspend fun fetchPaymentStore(storeId: Int): Result> { + return try { + val response = apiService.getDetailStore(storeId) + if (response.isSuccessful) { + val store = response.body()?.payment + if (store != null) { + Result.Success(store) + } else { + Result.Error(Exception("Payment details not found")) + } + } else { + val errorMsg = response.errorBody()?.string() ?: "Unknown error" + Log.e("Order Repository", "Error fetching store: $errorMsg") + Result.Error(Exception(errorMsg)) + } + } catch (e: Exception) { + Log.e("Order Repository", "Exception fetching payment details", e) + Result.Error(e) + } + } + suspend fun addAddress(request: CreateAddressRequest): Result { return try { Log.d("OrderRepository", "Adding address: $request") @@ -475,4 +497,27 @@ class OrderRepository(private val apiService: ApiService) { } }.flowOn(Dispatchers.IO) + suspend fun createReviewProduct(review: ReviewProductItem): Result{ + return try{ + Log.d("Order Repository", "Sending review item product: $review") + val response = apiService.createReview(review) + + if (response.isSuccessful){ + response.body()?.let { reviewProductResponse -> + Log.d("Order Repository", " Successful create review. Review item rating: ${reviewProductResponse.rating}, orderItemId: ${reviewProductResponse.orderItemId}") + Result.Success(reviewProductResponse) + } ?: run { + Result.Error(Exception("Failed to create review")) + } + } else { + val errorMsg = response.errorBody()?.string() ?: "Unknown Error" + Log.e("Order Repository", "Error create review. Code ${response.code()}, Error: $errorMsg") + Result.Error(Exception(errorMsg)) + } + } catch (e:Exception) { + Result.Error(e) + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt index 88dd5f6..b8d9c36 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt @@ -15,7 +15,7 @@ import androidx.recyclerview.widget.RecyclerView import com.alya.ecommerce_serang.data.api.dto.CheckoutData import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy -import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentInfoItem +import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentItemDetail import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding @@ -47,7 +47,6 @@ class CheckoutActivity : AppCompatActivity() { sessionManager = SessionManager(this) - // Setup UI components setupToolbar() setupObservers() @@ -100,9 +99,7 @@ class CheckoutActivity : AppCompatActivity() { updateOrderSummary() if (data != null) { - viewModel.getPaymentMethods { paymentMethods -> - Log.d("CheckoutActivity", "Loaded ${paymentMethods.size} payment methods") - } + viewModel.getPaymentMethods() } } @@ -122,7 +119,7 @@ class CheckoutActivity : AppCompatActivity() { viewModel.selectedPayment.observe(this) { selectedPayment -> if (selectedPayment != null) { - Log.d("CheckoutActivity", "Observer notified of selected payment: ${selectedPayment.name}") + Log.d("CheckoutActivity", "Observer notified of selected payment: ${selectedPayment.bankName}") // Update the adapter ONLY if it exists paymentAdapter?.let { adapter -> @@ -157,7 +154,7 @@ class CheckoutActivity : AppCompatActivity() { } } - private fun setupPaymentMethodsRecyclerView(paymentMethods: List) { + private fun setupPaymentMethodsRecyclerView(paymentMethods: List) { if (paymentMethods.isEmpty()) { Log.e("CheckoutActivity", "Payment methods list is empty") Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show() @@ -169,7 +166,7 @@ class CheckoutActivity : AppCompatActivity() { if (paymentAdapter == null) { paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment -> - Log.d("CheckoutActivity", "Payment selected in adapter: ${payment.name}") + Log.d("CheckoutActivity", "Payment selected in adapter: ${payment.bankName}") // Set this payment as selected in the ViewModel viewModel.setPaymentMethod(payment.id) @@ -182,7 +179,7 @@ class CheckoutActivity : AppCompatActivity() { } } - private fun updatePaymentMethodsAdapter(paymentMethods: List, selectedId: Int?) { + private fun updatePaymentMethodsAdapter(paymentMethods: List, selectedId: Int?) { Log.d("CheckoutActivity", "Updating payment adapter with ${paymentMethods.size} methods") // Simple test adapter @@ -198,7 +195,7 @@ class CheckoutActivity : AppCompatActivity() { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val payment = paymentMethods[position] - (holder.itemView as TextView).text = "Payment: ${payment.name}" + (holder.itemView as TextView).text = "Payment: ${payment.bankName}" } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt index cba73c1..429baac 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt @@ -10,7 +10,7 @@ import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart -import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentInfoItem +import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentItemDetail import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.Result @@ -24,12 +24,12 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { private val _addressDetails = MutableLiveData() val addressDetails: LiveData = _addressDetails - private val _availablePaymentMethods = MutableLiveData>() - val availablePaymentMethods: LiveData> = _availablePaymentMethods + private val _availablePaymentMethods = MutableLiveData>() + val availablePaymentMethods: LiveData> = _availablePaymentMethods // Selected payment method - private val _selectedPayment = MutableLiveData() - val selectedPayment: LiveData = _selectedPayment + private val _selectedPayment = MutableLiveData() + val selectedPayment: LiveData = _selectedPayment private val _isLoading = MutableLiveData() val isLoading: LiveData = _isLoading @@ -156,48 +156,45 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { } } - fun getPaymentMethods(callback: (List) -> Unit) { + fun getPaymentMethods() { viewModelScope.launch { try { - val storeId = _checkoutData.value?.sellerId ?: return@launch - - Log.d(TAG, "Attempting to fetch payment methods for storeId: $storeId") - - if (storeId == null || storeId <= 0) { - Log.e(TAG, "Invalid storeId: $storeId - cannot fetch payment methods") + val storeId = _checkoutData.value?.sellerId ?: run { + Log.e(TAG, "StoreId is null - cannot fetch payment methods") + _availablePaymentMethods.value = emptyList() return@launch } - // Use fetchStoreDetail instead of getStore - val storeResult = repository.fetchStoreDetail(storeId) + Log.d(TAG, "Attempting to fetch payment methods for storeId: $storeId") - if (storeResult is Result.Success && storeResult.data != null) { - // For now, we'll use hardcoded payment ID (1) for all payment methods - // This will be updated once the backend provides proper IDs - val paymentMethodsList = storeResult.data.paymentInfo.map { paymentInfo -> - PaymentInfoItem( - id = paymentInfo.id ?: 1, - name = paymentInfo.name, - bankNum = paymentInfo.bankNum, - qrisImage = paymentInfo.qrisImage - ) - } - - Log.d(TAG, "Fetched ${paymentMethodsList.size} payment methods") - - // Only update if we don't already have payment methods - if (_availablePaymentMethods.value.isNullOrEmpty()) { - _availablePaymentMethods.value = paymentMethodsList - } - callback(paymentMethodsList) - } else { + if (storeId <= 0) { + Log.e(TAG, "Invalid storeId: $storeId - cannot fetch payment methods") _availablePaymentMethods.value = emptyList() - callback(emptyList()) + return@launch + } + + val result = repository.fetchPaymentStore(storeId) + + when (result) { + is Result.Success -> { + val paymentMethods = result.data?.filterNotNull() ?: emptyList() + + Log.d(TAG, "Fetched ${paymentMethods.size} payment methods") + + // Update payment methods + _availablePaymentMethods.value = paymentMethods + } + is Result.Error -> { + Log.e(TAG, "Error fetching payment methods: ${result.exception.message}") + _availablePaymentMethods.value = emptyList() + } + is Result.Loading -> { + null + } } } catch (e: Exception) { - Log.e(TAG, "Error fetching payment methods", e) + Log.e(TAG, "Exception in getPaymentMethods", e) _availablePaymentMethods.value = emptyList() - callback(emptyList()) } } } @@ -211,7 +208,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { if (paymentMethods.isNullOrEmpty()) { // If no payment methods available, try to fetch them - getPaymentMethods { /* do nothing here */ } + getPaymentMethods() return@launch } @@ -224,7 +221,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { // Set the selected payment - IMPORTANT: do this first _selectedPayment.value = selectedPayment - Log.d(TAG, "Payment selected: ID=${selectedPayment.id}, Name=${selectedPayment.name}") + Log.d(TAG, "Payment selected: ID=${selectedPayment.id}, Name=${selectedPayment.bankName}") // Update the order request with the payment method ID val currentData = _checkoutData.value ?: return@launch diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt index dd2a20e..6028406 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt @@ -6,14 +6,14 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.alya.ecommerce_serang.R -import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentInfoItem +import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentItemDetail import com.alya.ecommerce_serang.databinding.ItemPaymentMethodBinding import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions class PaymentMethodAdapter( - private val paymentMethods: List, - private val onPaymentSelected: (PaymentInfoItem) -> Unit + private val paymentMethods: List, + private val onPaymentSelected: (PaymentItemDetail) -> Unit ) : RecyclerView.Adapter() { // Track the selected payment by ID @@ -38,13 +38,13 @@ class PaymentMethodAdapter( with(holder.binding) { // Set payment method name - tvPaymentMethodName.text = payment.name + tvPaymentMethodName.text = payment.bankName // Set radio button state based on selected payment ID rbPaymentMethod.isChecked = payment.id == selectedPaymentId // Debug log - Log.d("PaymentAdapter", "Binding item ${payment.name}, checked=${rbPaymentMethod.isChecked}") + Log.d("PaymentAdapter", "Binding item ${payment.bankName}, checked=${rbPaymentMethod.isChecked}") // Load payment icon if available if (!payment.qrisImage.isNullOrEmpty()) { @@ -84,7 +84,7 @@ class PaymentMethodAdapter( // Call the callback ONLY ONCE onPaymentSelected(payment) - Log.d("PaymentAdapter", "Payment selected: ${payment.name}") + Log.d("PaymentAdapter", "Payment selected: ${payment.bankName}") } } @@ -118,7 +118,7 @@ class PaymentMethodAdapter( } // Set selected payment object - fun setSelectedPayment(payment: PaymentInfoItem) { + fun setSelectedPayment(payment: PaymentItemDetail) { setSelectedPaymentId(payment.id) } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt index 694f1f7..4f02163 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt @@ -7,6 +7,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest import com.alya.ecommerce_serang.data.api.dto.OrdersItem +import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListItemsItem +import com.alya.ecommerce_serang.data.api.response.customer.order.Orders import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.Result @@ -26,6 +28,13 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() { private val _orderCompletionStatus = MutableLiveData>() val orderCompletionStatus: LiveData> = _orderCompletionStatus + private val _orderDetails = MutableLiveData() + val orderDetails: LiveData get() = _orderDetails + + // LiveData untuk OrderItems + private val _orderItems = MutableLiveData>() + val orderItems: LiveData> get() = _orderItems + private val _isLoading = MutableLiveData() val isLoading: LiveData = _isLoading @@ -35,6 +44,9 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() { private val _isSuccess = MutableLiveData() val isSuccess: LiveData = _isSuccess + private val _error = MutableLiveData() + val error: LiveData get() = _error + fun getOrderList(status: String) { _orders.value = ViewState.Loading viewModelScope.launch { @@ -99,4 +111,24 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() { } } } + + fun getOrderDetails(orderId: Int) { + _isLoading.value = true + viewModelScope.launch { + try { + val response = repository.getOrderDetails(orderId) + if (response != null) { + _orderDetails.value = response.orders + _orderItems.value = response.orders.orderItems + } else { + _error.value = "Gagal memuat detail pesanan" + } + } catch (e: Exception) { + _error.value = "Terjadi kesalahan: ${e.message}" + Log.e(TAG, "Error fetching order details", e) + } finally { + _isLoading.value = false + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt index c6c0b99..45c3297 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt @@ -22,9 +22,13 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.OrdersItem +import com.alya.ecommerce_serang.data.api.dto.ReviewUIItem import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity +import com.alya.ecommerce_serang.ui.order.review.CreateReviewActivity +import com.alya.ecommerce_serang.ui.product.ReviewProductActivity import com.google.android.material.button.MaterialButton import com.google.android.material.textfield.TextInputLayout +import com.google.gson.Gson import java.io.File import java.text.SimpleDateFormat import java.util.Calendar @@ -236,7 +240,7 @@ class OrderHistoryAdapter( } deadlineDate.apply { visibility = View.VISIBLE - text = formatShipmentDate(order.updatedAt, order.etd.toInt()) + text = formatShipmentDate(order.updatedAt, order.etd ?: "0") } } "delivered" -> { @@ -262,6 +266,7 @@ class OrderHistoryAdapter( visibility = View.VISIBLE text = itemView.context.getString(R.string.add_review) setOnClickListener { + addReviewProduct(order) // Handle click event } } @@ -322,9 +327,11 @@ class OrderHistoryAdapter( } } - private fun formatShipmentDate(dateString: String, estimate: Int): String { + private fun formatShipmentDate(dateString: String, estimate: String): String { return try { // Parse the input date + val estimateTD = if (estimate.isNullOrEmpty()) 0 else estimate.toInt() + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) inputFormat.timeZone = TimeZone.getTimeZone("UTC") @@ -339,7 +346,7 @@ class OrderHistoryAdapter( calendar.time = it // Add estimated days - calendar.add(Calendar.DAY_OF_MONTH, estimate) + calendar.add(Calendar.DAY_OF_MONTH, estimateTD) outputFormat.format(calendar.time) } ?: dateString } catch (e: Exception) { @@ -485,10 +492,70 @@ class OrderHistoryAdapter( } dialog.show() } + + private fun addReviewProduct(order: OrdersItem) { + // Use ViewModel to fetch order details + viewModel.getOrderDetails(order.orderId) + + // Create loading dialog +// val loadingDialog = Dialog(itemView.context).apply { +// requestWindowFeature(Window.FEATURE_NO_TITLE) +// setContentView(R.layout.dialog_loading) +// window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) +// setCancelable(false) +// } +// loadingDialog.show() + + viewModel.error.observe(itemView.findViewTreeLifecycleOwner()!!) { errorMsg -> + if (!errorMsg.isNullOrEmpty()) { + Toast.makeText(itemView.context, errorMsg, Toast.LENGTH_SHORT).show() + } + } + + // Observe the order details result + viewModel.orderItems.observe(itemView.findViewTreeLifecycleOwner()!!) { orderItems -> + if (orderItems != null && orderItems.isNotEmpty()) { + // For single item review + if (orderItems.size == 1) { + val item = orderItems[0] + val intent = Intent(itemView.context, CreateReviewActivity::class.java).apply { + putExtra("order_item_id", item.orderItemId) + putExtra("product_name", item.productName) + putExtra("product_image", item.productImage) + } + (itemView.context as Activity).startActivityForResult(intent, REQUEST_CODE_REVIEW) + } + // For multiple items + else { + val reviewItems = orderItems.map { item -> + ReviewUIItem( + orderItemId = item.orderItemId, + productName = item.productName, + productImage = item.productImage + ) + } + + val itemsJson = Gson().toJson(reviewItems) + val intent = Intent(itemView.context, ReviewProductActivity::class.java).apply { + putExtra("order_items", itemsJson) + } + (itemView.context as Activity).startActivityForResult(intent, REQUEST_CODE_REVIEW) + } + } else { + Toast.makeText( + itemView.context, + "No items to review", + Toast.LENGTH_SHORT + ).show() + } + } + } + } companion object { private const val REQUEST_IMAGE_PICK = 100 + const val REQUEST_CODE_REVIEW = 101 private var imagePickCallback: ((Uri) -> Unit)? = null // This method should be called from the activity's onActivityResult diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/AddReviewAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/AddReviewAdapter.kt new file mode 100644 index 0000000..248d47a --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/AddReviewAdapter.kt @@ -0,0 +1,64 @@ +package com.alya.ecommerce_serang.ui.order.review + +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.ReviewUIItem +import com.alya.ecommerce_serang.databinding.ItemReviewProductBinding +import com.bumptech.glide.Glide + +class AddReviewAdapter( + private val items: List, + private val onRatingChanged: (position: Int, rating: Int) -> Unit, + private val onReviewTextChanged: (position: Int, text: String) -> Unit +) : RecyclerView.Adapter() { + + inner class AddReviewViewHolder(private val binding: ItemReviewProductBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: ReviewUIItem) { + binding.apply { + tvProductName.text = item.productName + + Glide.with(itemView.context) + .load(item.productImage) + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.placeholder_image) + .into(ivProduct) + + ratingBar.rating = item.rating.toFloat() + etReviewText.setText(item.reviewText) + + ratingBar.setOnRatingBarChangeListener { _, rating, _ -> + onRatingChanged(adapterPosition, rating.toInt()) + } + + etReviewText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} + override fun afterTextChanged(editable: Editable?) { + onReviewTextChanged(adapterPosition, editable.toString()) + } + }) + + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddReviewViewHolder { + val binding = ItemReviewProductBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return AddReviewViewHolder(binding) + } + + override fun onBindViewHolder(holder: AddReviewViewHolder, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount(): Int = items.size +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewActivity.kt new file mode 100644 index 0000000..1fbbc93 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewActivity.kt @@ -0,0 +1,188 @@ +package com.alya.ecommerce_serang.ui.order.review + +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.recyclerview.widget.LinearLayoutManager +import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem +import com.alya.ecommerce_serang.data.api.dto.ReviewUIItem +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.OrderRepository +import com.alya.ecommerce_serang.data.repository.Result +import com.alya.ecommerce_serang.databinding.ActivityCreateReviewBinding +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class CreateReviewActivity : AppCompatActivity() { + + private lateinit var binding: ActivityCreateReviewBinding + private lateinit var sessionManager: SessionManager + private val reviewItems = mutableListOf() + private var addReviewAdapter: AddReviewAdapter? = null + + private val viewModel : CreateReviewViewModel by viewModels { + BaseViewModelFactory { + val apiService = ApiConfig.getApiService(sessionManager) + val orderRepository = OrderRepository(apiService) + CreateReviewViewModel(orderRepository) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityCreateReviewBinding.inflate(layoutInflater) + setContentView(binding.root) + + sessionManager = SessionManager(this) + + WindowCompat.setDecorFitsSystemWindows(window, false) + + enableEdgeToEdge() + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.setPadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom + ) + windowInsets + } + + setupToolbar() + getIntentData() + setupRecyclerView() + observeViewModel() + setupSubmitButton() + } + + private fun setupToolbar() { + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayShowTitleEnabled(false) + binding.btnBack.setOnClickListener { onBackPressed() } + } + + private fun getIntentData() { + // First check if multiple items were passed + val orderItemsJson = intent.getStringExtra("order_items") + if (orderItemsJson != null) { + try { + val type = object : TypeToken>() {}.type + val items: List = Gson().fromJson(orderItemsJson, type) + + // Make sure we explicitly set rating and reviewText + reviewItems.addAll(items.map { item -> + ReviewUIItem( + orderItemId = item.orderItemId, + productName = item.productName, + productImage = item.productImage, + rating = 5, // Default to 5 stars + reviewText = "" // Empty by default + ) + }) + } catch (e: Exception) { + Toast.makeText(this, "Error loading review items", Toast.LENGTH_SHORT).show() + finish() + } + } else { + // Check if a single item was passed + val orderItemId = intent.getIntExtra("order_item_id", -1) + val productName = intent.getStringExtra("product_name") ?: "" + val productImage = intent.getStringExtra("product_image") ?: "" + + if (orderItemId != -1) { + reviewItems.add( + ReviewUIItem( + orderItemId = orderItemId, + productName = productName, + productImage = productImage, + rating = 5, // Default to 5 stars + reviewText = "" // Empty by default + ) + ) + } else { + Toast.makeText(this, "No items to review", Toast.LENGTH_SHORT).show() + finish() + } + } + } + + private fun setupRecyclerView() { + addReviewAdapter = AddReviewAdapter( + reviewItems, + onRatingChanged = { position, rating -> + reviewItems[position].rating = rating + }, + onReviewTextChanged = { position, text -> + reviewItems[position].reviewText = text + } + ) + + binding.rvReviewItems.apply { + layoutManager = LinearLayoutManager(this@CreateReviewActivity) + adapter = addReviewAdapter + } + } + + private fun observeViewModel() { + viewModel.reviewSubmitStatus.observe(this) { result -> + when (result) { + is Result.Loading -> { + // Show loading indicator + // You can add a ProgressBar in your layout and show/hide it here + } + is Result.Success -> { + // All reviews submitted successfully + Toast.makeText(this, "Ulasan berhasil dikirim", Toast.LENGTH_SHORT).show() + setResult(RESULT_OK) + finish() + } + is Result.Error -> { + // Show error message + Log.e("CreateReviewActivity", "Error: ${result.exception}") +// Toast.makeText(this, result.message, Toast.LENGTH_SHORT).show() + } + } + } + } + + private fun setupSubmitButton() { + binding.btnSubmitReview.setOnClickListener { + // Validate all reviews + var isValid = true + for (item in reviewItems) { + if (item.reviewText.isBlank()) { + isValid = false + Toast.makeText(this, "Mohon isi semua ulasan", Toast.LENGTH_SHORT).show() + break + } + } + + // In setupSubmitButton() method + if (isValid) { + viewModel.setTotalReviewsToSubmit(reviewItems.size) + + // Submit all reviews + for (item in reviewItems) { + Log.d("ReviewActivity", "Submitting review for item ${item.orderItemId}: rating=${item.rating}, text=${item.reviewText}") + + val reviewProductItem = ReviewProductItem( + orderItemId = item.orderItemId, + rating = item.rating, + reviewTxt = item.reviewText + ) + viewModel.submitReview(reviewProductItem) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewViewModel.kt new file mode 100644 index 0000000..6c05ab0 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/review/CreateReviewViewModel.kt @@ -0,0 +1,41 @@ +package com.alya.ecommerce_serang.ui.order.review + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem +import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse +import com.alya.ecommerce_serang.data.repository.OrderRepository +import com.alya.ecommerce_serang.data.repository.Result +import kotlinx.coroutines.launch + +class CreateReviewViewModel(private val repository: OrderRepository): ViewModel() { + private val _reviewSubmitStatus = MutableLiveData>() + val reviewSubmitStatus: LiveData> = _reviewSubmitStatus + + private val _reviewsSubmitted = MutableLiveData(0) + private var totalReviewsToSubmit = 0 + private var anyFailures = false + + fun submitReview(reviewItem: ReviewProductItem) { + viewModelScope.launch { + try { + _reviewSubmitStatus.value = Result.Loading + val result = repository.createReviewProduct(reviewItem) + _reviewSubmitStatus.value = result + } catch (e: Exception) { + anyFailures = true + Log.e("CreateReviewViewModel", "Error create review: ${e.message}") + _reviewSubmitStatus.value = Result.Error(e) + } + } + } + + fun setTotalReviewsToSubmit(count: Int) { + totalReviewsToSubmit = count + _reviewsSubmitted.value = 0 + anyFailures = false + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_review.xml b/app/src/main/res/layout/activity_create_review.xml new file mode 100644 index 0000000..b516a95 --- /dev/null +++ b/app/src/main/res/layout/activity_create_review.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_review_product.xml b/app/src/main/res/layout/item_review_product.xml new file mode 100644 index 0000000..c6767a9 --- /dev/null +++ b/app/src/main/res/layout/item_review_product.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 25b40eb..c41219f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -41,6 +41,7 @@ #E8ECF2 #7D8FAB #489EC6 + #faf069 #489EC6 #8E8E8E