mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
update add review, fix cart, fix order
This commit is contained in:
@ -29,6 +29,9 @@
|
|||||||
android:theme="@style/Theme.Ecommerce_serang"
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.order.review.CreateReviewActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.cart.MainActivity"
|
android:name=".ui.cart.MainActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -10,5 +10,13 @@ data class ReviewProductItem (
|
|||||||
val rating : Int,
|
val rating : Int,
|
||||||
|
|
||||||
@SerializedName("review_text")
|
@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
|
||||||
)
|
)
|
@ -4,6 +4,12 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
data class DetailStoreProductResponse(
|
data class DetailStoreProductResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("shipping")
|
||||||
|
val shipping: List<ShippingItemDetail>,
|
||||||
|
|
||||||
|
@field:SerializedName("payment")
|
||||||
|
val payment: List<PaymentItemDetail>,
|
||||||
|
|
||||||
@field:SerializedName("store")
|
@field:SerializedName("store")
|
||||||
val store: StoreProduct,
|
val store: StoreProduct,
|
||||||
|
|
||||||
@ -11,28 +17,11 @@ data class DetailStoreProductResponse(
|
|||||||
val message: String
|
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(
|
data class StoreProduct(
|
||||||
|
|
||||||
@field:SerializedName("store_id")
|
@field:SerializedName("store_id")
|
||||||
val storeId: Int,
|
val storeId: Int,
|
||||||
|
|
||||||
@field:SerializedName("shipping_service")
|
|
||||||
val shippingService: List<ShippingServiceItem>,
|
|
||||||
|
|
||||||
@field:SerializedName("store_rating")
|
@field:SerializedName("store_rating")
|
||||||
val storeRating: String,
|
val storeRating: String,
|
||||||
|
|
||||||
@ -45,21 +34,36 @@ data class StoreProduct(
|
|||||||
@field:SerializedName("store_type")
|
@field:SerializedName("store_type")
|
||||||
val storeType: String,
|
val storeType: String,
|
||||||
|
|
||||||
@field:SerializedName("payment_info")
|
|
||||||
val paymentInfo: List<PaymentInfoItem>,
|
|
||||||
|
|
||||||
@field:SerializedName("store_location")
|
@field:SerializedName("store_location")
|
||||||
val storeLocation: String,
|
val storeLocation: String,
|
||||||
|
|
||||||
@field:SerializedName("store_image")
|
@field:SerializedName("store_image")
|
||||||
val storeImage: String,
|
val storeImage: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("status")
|
@field:SerializedName("status")
|
||||||
val status: String
|
val status: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ShippingServiceItem(
|
data class ShippingItemDetail(
|
||||||
|
|
||||||
@field:SerializedName("courier")
|
@field:SerializedName("courier")
|
||||||
val courier: String
|
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
|
||||||
|
)
|
||||||
|
@ -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.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
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.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.UpdateCart
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
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.cart.DataItemCart
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
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.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.ListCityResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
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.OrderDetailResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse
|
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.ProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct
|
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
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<String> {
|
suspend fun updateCart(updateCart: UpdateCart): Result<String> {
|
||||||
return try {
|
return try {
|
||||||
val response = apiService.updateCart(updateCart)
|
val response = apiService.updateCart(updateCart)
|
||||||
@ -236,6 +237,27 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun fetchPaymentStore(storeId: Int): Result<List<PaymentItemDetail?>> {
|
||||||
|
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<CreateAddressResponse> {
|
suspend fun addAddress(request: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||||
return try {
|
return try {
|
||||||
Log.d("OrderRepository", "Adding address: $request")
|
Log.d("OrderRepository", "Adding address: $request")
|
||||||
@ -475,4 +497,27 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
}
|
}
|
||||||
}.flowOn(Dispatchers.IO)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
|
suspend fun createReviewProduct(review: ReviewProductItem): Result<CreateReviewResponse>{
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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.CheckoutData
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
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.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.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding
|
import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding
|
||||||
@ -47,7 +47,6 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
sessionManager = SessionManager(this)
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
|
||||||
// Setup UI components
|
// Setup UI components
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
@ -100,9 +99,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
updateOrderSummary()
|
updateOrderSummary()
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
viewModel.getPaymentMethods { paymentMethods ->
|
viewModel.getPaymentMethods()
|
||||||
Log.d("CheckoutActivity", "Loaded ${paymentMethods.size} payment methods")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +119,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
viewModel.selectedPayment.observe(this) { selectedPayment ->
|
viewModel.selectedPayment.observe(this) { selectedPayment ->
|
||||||
if (selectedPayment != null) {
|
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
|
// Update the adapter ONLY if it exists
|
||||||
paymentAdapter?.let { adapter ->
|
paymentAdapter?.let { adapter ->
|
||||||
@ -157,7 +154,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentInfoItem>) {
|
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentItemDetail>) {
|
||||||
if (paymentMethods.isEmpty()) {
|
if (paymentMethods.isEmpty()) {
|
||||||
Log.e("CheckoutActivity", "Payment methods list is empty")
|
Log.e("CheckoutActivity", "Payment methods list is empty")
|
||||||
Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show()
|
||||||
@ -169,7 +166,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
if (paymentAdapter == null) {
|
if (paymentAdapter == null) {
|
||||||
paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment ->
|
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
|
// Set this payment as selected in the ViewModel
|
||||||
viewModel.setPaymentMethod(payment.id)
|
viewModel.setPaymentMethod(payment.id)
|
||||||
@ -182,7 +179,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePaymentMethodsAdapter(paymentMethods: List<PaymentInfoItem>, selectedId: Int?) {
|
private fun updatePaymentMethodsAdapter(paymentMethods: List<PaymentItemDetail>, selectedId: Int?) {
|
||||||
Log.d("CheckoutActivity", "Updating payment adapter with ${paymentMethods.size} methods")
|
Log.d("CheckoutActivity", "Updating payment adapter with ${paymentMethods.size} methods")
|
||||||
|
|
||||||
// Simple test adapter
|
// Simple test adapter
|
||||||
@ -198,7 +195,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
val payment = paymentMethods[position]
|
val payment = paymentMethods[position]
|
||||||
(holder.itemView as TextView).text = "Payment: ${payment.name}"
|
(holder.itemView as TextView).text = "Payment: ${payment.bankName}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.dto.OrderRequestBuy
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
|
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.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.api.response.customer.profile.AddressesItem
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
@ -24,12 +24,12 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
||||||
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
||||||
|
|
||||||
private val _availablePaymentMethods = MutableLiveData<List<PaymentInfoItem>>()
|
private val _availablePaymentMethods = MutableLiveData<List<PaymentItemDetail>>()
|
||||||
val availablePaymentMethods: LiveData<List<PaymentInfoItem>> = _availablePaymentMethods
|
val availablePaymentMethods: LiveData<List<PaymentItemDetail>> = _availablePaymentMethods
|
||||||
|
|
||||||
// Selected payment method
|
// Selected payment method
|
||||||
private val _selectedPayment = MutableLiveData<PaymentInfoItem?>()
|
private val _selectedPayment = MutableLiveData<PaymentItemDetail?>()
|
||||||
val selectedPayment: LiveData<PaymentInfoItem?> = _selectedPayment
|
val selectedPayment: LiveData<PaymentItemDetail?> = _selectedPayment
|
||||||
|
|
||||||
private val _isLoading = MutableLiveData<Boolean>()
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
val isLoading: LiveData<Boolean> = _isLoading
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
@ -156,48 +156,45 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPaymentMethods(callback: (List<PaymentInfoItem>) -> Unit) {
|
fun getPaymentMethods() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val storeId = _checkoutData.value?.sellerId ?: return@launch
|
val storeId = _checkoutData.value?.sellerId ?: run {
|
||||||
|
Log.e(TAG, "StoreId is null - cannot fetch payment methods")
|
||||||
Log.d(TAG, "Attempting to fetch payment methods for storeId: $storeId")
|
_availablePaymentMethods.value = emptyList()
|
||||||
|
|
||||||
if (storeId == null || storeId <= 0) {
|
|
||||||
Log.e(TAG, "Invalid storeId: $storeId - cannot fetch payment methods")
|
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use fetchStoreDetail instead of getStore
|
Log.d(TAG, "Attempting to fetch payment methods for storeId: $storeId")
|
||||||
val storeResult = repository.fetchStoreDetail(storeId)
|
|
||||||
|
|
||||||
if (storeResult is Result.Success && storeResult.data != null) {
|
if (storeId <= 0) {
|
||||||
// For now, we'll use hardcoded payment ID (1) for all payment methods
|
Log.e(TAG, "Invalid storeId: $storeId - cannot fetch 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 {
|
|
||||||
_availablePaymentMethods.value = emptyList()
|
_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) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error fetching payment methods", e)
|
Log.e(TAG, "Exception in getPaymentMethods", e)
|
||||||
_availablePaymentMethods.value = emptyList()
|
_availablePaymentMethods.value = emptyList()
|
||||||
callback(emptyList())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,7 +208,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
|
|
||||||
if (paymentMethods.isNullOrEmpty()) {
|
if (paymentMethods.isNullOrEmpty()) {
|
||||||
// If no payment methods available, try to fetch them
|
// If no payment methods available, try to fetch them
|
||||||
getPaymentMethods { /* do nothing here */ }
|
getPaymentMethods()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +221,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
|
|
||||||
// Set the selected payment - IMPORTANT: do this first
|
// Set the selected payment - IMPORTANT: do this first
|
||||||
_selectedPayment.value = selectedPayment
|
_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
|
// Update the order request with the payment method ID
|
||||||
val currentData = _checkoutData.value ?: return@launch
|
val currentData = _checkoutData.value ?: return@launch
|
||||||
|
@ -6,14 +6,14 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.alya.ecommerce_serang.R
|
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.alya.ecommerce_serang.databinding.ItemPaymentMethodBinding
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
|
||||||
class PaymentMethodAdapter(
|
class PaymentMethodAdapter(
|
||||||
private val paymentMethods: List<PaymentInfoItem>,
|
private val paymentMethods: List<PaymentItemDetail>,
|
||||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
private val onPaymentSelected: (PaymentItemDetail) -> Unit
|
||||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||||
|
|
||||||
// Track the selected payment by ID
|
// Track the selected payment by ID
|
||||||
@ -38,13 +38,13 @@ class PaymentMethodAdapter(
|
|||||||
|
|
||||||
with(holder.binding) {
|
with(holder.binding) {
|
||||||
// Set payment method name
|
// Set payment method name
|
||||||
tvPaymentMethodName.text = payment.name
|
tvPaymentMethodName.text = payment.bankName
|
||||||
|
|
||||||
// Set radio button state based on selected payment ID
|
// Set radio button state based on selected payment ID
|
||||||
rbPaymentMethod.isChecked = payment.id == selectedPaymentId
|
rbPaymentMethod.isChecked = payment.id == selectedPaymentId
|
||||||
|
|
||||||
// Debug log
|
// 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
|
// Load payment icon if available
|
||||||
if (!payment.qrisImage.isNullOrEmpty()) {
|
if (!payment.qrisImage.isNullOrEmpty()) {
|
||||||
@ -84,7 +84,7 @@ class PaymentMethodAdapter(
|
|||||||
// Call the callback ONLY ONCE
|
// Call the callback ONLY ONCE
|
||||||
onPaymentSelected(payment)
|
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
|
// Set selected payment object
|
||||||
fun setSelectedPayment(payment: PaymentInfoItem) {
|
fun setSelectedPayment(payment: PaymentItemDetail) {
|
||||||
setSelectedPaymentId(payment.id)
|
setSelectedPaymentId(payment.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,6 +7,8 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
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.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.api.response.order.CompletedOrderResponse
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
@ -26,6 +28,13 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
private val _orderCompletionStatus = MutableLiveData<Result<CompletedOrderResponse>>()
|
private val _orderCompletionStatus = MutableLiveData<Result<CompletedOrderResponse>>()
|
||||||
val orderCompletionStatus: LiveData<Result<CompletedOrderResponse>> = _orderCompletionStatus
|
val orderCompletionStatus: LiveData<Result<CompletedOrderResponse>> = _orderCompletionStatus
|
||||||
|
|
||||||
|
private val _orderDetails = MutableLiveData<Orders>()
|
||||||
|
val orderDetails: LiveData<Orders> get() = _orderDetails
|
||||||
|
|
||||||
|
// LiveData untuk OrderItems
|
||||||
|
private val _orderItems = MutableLiveData<List<OrderListItemsItem>>()
|
||||||
|
val orderItems: LiveData<List<OrderListItemsItem>> get() = _orderItems
|
||||||
|
|
||||||
private val _isLoading = MutableLiveData<Boolean>()
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
val isLoading: LiveData<Boolean> = _isLoading
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
|
|
||||||
@ -35,6 +44,9 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
private val _isSuccess = MutableLiveData<Boolean>()
|
private val _isSuccess = MutableLiveData<Boolean>()
|
||||||
val isSuccess: LiveData<Boolean> = _isSuccess
|
val isSuccess: LiveData<Boolean> = _isSuccess
|
||||||
|
|
||||||
|
private val _error = MutableLiveData<String>()
|
||||||
|
val error: LiveData<String> get() = _error
|
||||||
|
|
||||||
fun getOrderList(status: String) {
|
fun getOrderList(status: String) {
|
||||||
_orders.value = ViewState.Loading
|
_orders.value = ViewState.Loading
|
||||||
viewModelScope.launch {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,9 +22,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
|
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.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.button.MaterialButton
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.google.gson.Gson
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -236,7 +240,7 @@ class OrderHistoryAdapter(
|
|||||||
}
|
}
|
||||||
deadlineDate.apply {
|
deadlineDate.apply {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
text = formatShipmentDate(order.updatedAt, order.etd.toInt())
|
text = formatShipmentDate(order.updatedAt, order.etd ?: "0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"delivered" -> {
|
"delivered" -> {
|
||||||
@ -262,6 +266,7 @@ class OrderHistoryAdapter(
|
|||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
text = itemView.context.getString(R.string.add_review)
|
text = itemView.context.getString(R.string.add_review)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
|
addReviewProduct(order)
|
||||||
// Handle click event
|
// 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 {
|
return try {
|
||||||
// Parse the input date
|
// 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())
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
@ -339,7 +346,7 @@ class OrderHistoryAdapter(
|
|||||||
calendar.time = it
|
calendar.time = it
|
||||||
|
|
||||||
// Add estimated days
|
// Add estimated days
|
||||||
calendar.add(Calendar.DAY_OF_MONTH, estimate)
|
calendar.add(Calendar.DAY_OF_MONTH, estimateTD)
|
||||||
outputFormat.format(calendar.time)
|
outputFormat.format(calendar.time)
|
||||||
} ?: dateString
|
} ?: dateString
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -485,10 +492,70 @@ class OrderHistoryAdapter(
|
|||||||
}
|
}
|
||||||
dialog.show()
|
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 {
|
companion object {
|
||||||
private const val REQUEST_IMAGE_PICK = 100
|
private const val REQUEST_IMAGE_PICK = 100
|
||||||
|
const val REQUEST_CODE_REVIEW = 101
|
||||||
private var imagePickCallback: ((Uri) -> Unit)? = null
|
private var imagePickCallback: ((Uri) -> Unit)? = null
|
||||||
|
|
||||||
// This method should be called from the activity's onActivityResult
|
// This method should be called from the activity's onActivityResult
|
||||||
|
@ -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<ReviewUIItem>,
|
||||||
|
private val onRatingChanged: (position: Int, rating: Int) -> Unit,
|
||||||
|
private val onReviewTextChanged: (position: Int, text: String) -> Unit
|
||||||
|
) : RecyclerView.Adapter<AddReviewAdapter.AddReviewViewHolder>() {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -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<ReviewUIItem>()
|
||||||
|
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<List<ReviewUIItem>>() {}.type
|
||||||
|
val items: List<ReviewUIItem> = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Result<CreateReviewResponse>>()
|
||||||
|
val reviewSubmitStatus: LiveData<Result<CreateReviewResponse>> = _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
|
||||||
|
}
|
||||||
|
}
|
72
app/src/main/res/layout/activity_create_review.xml
Normal file
72
app/src/main/res/layout/activity_create_review.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?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.order.review.CreateReviewActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appBarLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@color/white">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnBack"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_back_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Tambah Ulasan"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btnBack"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnSubmitReview"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="Kirim Ulasan"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvReviewItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
84
app/src/main/res/layout/item_review_product.xml
Normal file
84
app/src/main/res/layout/item_review_product.xml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivProduct"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="Keripik Balado"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Rating"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<RatingBar
|
||||||
|
android:id="@+id/ratingBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:isIndicator="false"
|
||||||
|
android:numStars="5"
|
||||||
|
android:progressTint="@color/yellow"
|
||||||
|
android:stepSize="1.0" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Ulasan"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etReviewText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/bg_edit_text_background"
|
||||||
|
android:gravity="top"
|
||||||
|
android:hint="Isi jawaban Anda di sini"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:lines="4"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
@ -41,6 +41,7 @@
|
|||||||
<color name="gray_1">#E8ECF2</color>
|
<color name="gray_1">#E8ECF2</color>
|
||||||
<color name="soft_gray">#7D8FAB</color>
|
<color name="soft_gray">#7D8FAB</color>
|
||||||
<color name="blue1">#489EC6</color>
|
<color name="blue1">#489EC6</color>
|
||||||
|
<color name="yellow">#faf069</color>
|
||||||
|
|
||||||
<color name="bottom_navigation_icon_color_active">#489EC6</color>
|
<color name="bottom_navigation_icon_color_active">#489EC6</color>
|
||||||
<color name="bottom_navigation_icon_color_inactive">#8E8E8E</color>
|
<color name="bottom_navigation_icon_color_inactive">#8E8E8E</color>
|
||||||
|
Reference in New Issue
Block a user