mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22: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:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.order.review.CreateReviewActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.cart.MainActivity"
|
||||
android:exported="false" />
|
||||
|
@ -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
|
||||
)
|
@ -4,6 +4,12 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DetailStoreProductResponse(
|
||||
|
||||
@field:SerializedName("shipping")
|
||||
val shipping: List<ShippingItemDetail>,
|
||||
|
||||
@field:SerializedName("payment")
|
||||
val payment: List<PaymentItemDetail>,
|
||||
|
||||
@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<ShippingServiceItem>,
|
||||
|
||||
@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<PaymentInfoItem>,
|
||||
|
||||
@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
|
||||
)
|
||||
|
@ -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<String> {
|
||||
return try {
|
||||
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> {
|
||||
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<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.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<PaymentInfoItem>) {
|
||||
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentItemDetail>) {
|
||||
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<PaymentInfoItem>, selectedId: Int?) {
|
||||
private fun updatePaymentMethodsAdapter(paymentMethods: List<PaymentItemDetail>, 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}"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<AddressesItem?>()
|
||||
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
||||
|
||||
private val _availablePaymentMethods = MutableLiveData<List<PaymentInfoItem>>()
|
||||
val availablePaymentMethods: LiveData<List<PaymentInfoItem>> = _availablePaymentMethods
|
||||
private val _availablePaymentMethods = MutableLiveData<List<PaymentItemDetail>>()
|
||||
val availablePaymentMethods: LiveData<List<PaymentItemDetail>> = _availablePaymentMethods
|
||||
|
||||
// Selected payment method
|
||||
private val _selectedPayment = MutableLiveData<PaymentInfoItem?>()
|
||||
val selectedPayment: LiveData<PaymentInfoItem?> = _selectedPayment
|
||||
private val _selectedPayment = MutableLiveData<PaymentItemDetail?>()
|
||||
val selectedPayment: LiveData<PaymentItemDetail?> = _selectedPayment
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
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 {
|
||||
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
|
||||
|
@ -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<PaymentInfoItem>,
|
||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
||||
private val paymentMethods: List<PaymentItemDetail>,
|
||||
private val onPaymentSelected: (PaymentItemDetail) -> Unit
|
||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
@ -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<Result<CompletedOrderResponse>>()
|
||||
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>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
@ -35,6 +44,9 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
private val _isSuccess = MutableLiveData<Boolean>()
|
||||
val isSuccess: LiveData<Boolean> = _isSuccess
|
||||
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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="soft_gray">#7D8FAB</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_inactive">#8E8E8E</color>
|
||||
|
Reference in New Issue
Block a user