mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-12 18:22:22 +00:00
update detail 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.history.detailorder.DetailOrderStatusActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.order.review.CreateReviewActivity"
|
android:name=".ui.order.review.CreateReviewActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -7,9 +7,13 @@ import android.util.Log
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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 androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||||
@ -47,6 +51,22 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
sessionManager = SessionManager(this)
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
// Apply insets to your root layout
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||||
|
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
view.setPadding(
|
||||||
|
systemBars.left,
|
||||||
|
systemBars.top,
|
||||||
|
systemBars.right,
|
||||||
|
systemBars.bottom
|
||||||
|
)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
|
||||||
// Setup UI components
|
// Setup UI components
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
|
@ -329,7 +329,6 @@ class OrderHistoryAdapter(
|
|||||||
|
|
||||||
private fun formatShipmentDate(dateString: String, estimate: String): String {
|
private fun formatShipmentDate(dateString: String, estimate: String): String {
|
||||||
return try {
|
return try {
|
||||||
// Parse the input date
|
|
||||||
val estimateTD = if (estimate.isNullOrEmpty()) 0 else estimate.toInt()
|
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())
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.history
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -14,6 +17,7 @@ import com.alya.ecommerce_serang.data.repository.OrderRepository
|
|||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentOrderListBinding
|
import com.alya.ecommerce_serang.databinding.FragmentOrderListBinding
|
||||||
import com.alya.ecommerce_serang.ui.order.address.ViewState
|
import com.alya.ecommerce_serang.ui.order.address.ViewState
|
||||||
|
import com.alya.ecommerce_serang.ui.order.history.detailorder.DetailOrderStatusActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
@ -121,10 +125,21 @@ class OrderListFragment : Fragment() {
|
|||||||
viewModel.getOrderList(status)
|
viewModel.getOrderList(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val detailOrderLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
// Refresh order list when returning with OK result
|
||||||
|
viewModel.getOrderList(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun navigateToOrderDetail(order: OrdersItem) {
|
private fun navigateToOrderDetail(order: OrdersItem) {
|
||||||
// In a real app, you would navigate to order detail screen
|
val intent = Intent(requireContext(), DetailOrderStatusActivity::class.java).apply {
|
||||||
// For example: findNavController().navigate(OrderListFragmentDirections.actionToOrderDetail(order.orderId))
|
putExtra("ORDER_ID", order.orderId)
|
||||||
Toast.makeText(requireContext(), "Order ID: ${order.orderId}", Toast.LENGTH_SHORT).show()
|
putExtra("ORDER_STATUS", status) // Pass the current status
|
||||||
|
}
|
||||||
|
detailOrderLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -148,4 +163,5 @@ class OrderListFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history.detailorder
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListItemsItem
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
|
class DetailOrderItemsAdapter : RecyclerView.Adapter<DetailOrderItemsAdapter.DetailOrderItemViewHolder>() {
|
||||||
|
|
||||||
|
private val items = mutableListOf<OrderListItemsItem>()
|
||||||
|
|
||||||
|
fun submitList(newItems: List<OrderListItemsItem>) {
|
||||||
|
items.clear()
|
||||||
|
items.addAll(newItems)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailOrderItemViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_order_detail_product, parent, false)
|
||||||
|
return DetailOrderItemViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: DetailOrderItemViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
|
inner class DetailOrderItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
private val ivProduct: ImageView = itemView.findViewById(R.id.ivProduct)
|
||||||
|
private val tvProductName: TextView = itemView.findViewById(R.id.tvProductName)
|
||||||
|
private val tvQuantity: TextView = itemView.findViewById(R.id.tvQuantity)
|
||||||
|
private val tvPrice: TextView = itemView.findViewById(R.id.tvPrice)
|
||||||
|
|
||||||
|
fun bind(item: OrderListItemsItem) {
|
||||||
|
// Load product image
|
||||||
|
Glide.with(itemView.context)
|
||||||
|
.load(item.productImage)
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
.error(R.drawable.placeholder_image)
|
||||||
|
.into(ivProduct)
|
||||||
|
|
||||||
|
tvProductName.text = item.productName
|
||||||
|
tvQuantity.text = "${item.quantity} buah"
|
||||||
|
tvPrice.text = "Rp${item.price}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,683 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history.detailorder
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
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.R
|
||||||
|
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.ReviewUIItem
|
||||||
|
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.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityDetailOrderStatusBinding
|
||||||
|
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.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
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
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class DetailOrderStatusActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityDetailOrderStatusBinding
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private var orderId: Int = -1
|
||||||
|
private var orderStatus: String = ""
|
||||||
|
private val orders = mutableListOf<OrdersItem>()
|
||||||
|
private var selectedImageUri: Uri? = null
|
||||||
|
|
||||||
|
private var cancelDialog: Dialog? = null
|
||||||
|
private var dialogImageView: ImageView? = null
|
||||||
|
private var dialogSelectTextView: TextView? = null
|
||||||
|
|
||||||
|
private val viewModel: DetailOrderViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
DetailOrderViewModel(orderRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.d(TAG, "onCreate: Starting activity initialization")
|
||||||
|
|
||||||
|
binding = ActivityDetailOrderStatusBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
// Apply insets to your root layout
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||||
|
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
view.setPadding(
|
||||||
|
systemBars.left,
|
||||||
|
systemBars.top,
|
||||||
|
systemBars.right,
|
||||||
|
systemBars.bottom
|
||||||
|
)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
|
||||||
|
orderId = intent.getIntExtra("ORDER_ID", -1)
|
||||||
|
orderStatus = intent.getStringExtra("ORDER_STATUS") ?: ""
|
||||||
|
|
||||||
|
Log.d(TAG, "onCreate: orderID=$orderId, orderStatus=$orderStatus")
|
||||||
|
|
||||||
|
if (orderId == -1) {
|
||||||
|
Log.e(TAG, "onCreate: Invalid order ID received")
|
||||||
|
Toast.makeText(this, "Invalid order ID", Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setupObservers()
|
||||||
|
loadOrderDetails()
|
||||||
|
|
||||||
|
Log.d(TAG, "onCreate: Activity initialization completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
Log.d(TAG, "setupObservers: Setting up LiveData observers")
|
||||||
|
|
||||||
|
// Observe order details
|
||||||
|
viewModel.orderDetails.observe(this) { orders ->
|
||||||
|
if (orders != null) {
|
||||||
|
Log.d(TAG, "Observer: orderDetails received, orderId=${orders.orderId}")
|
||||||
|
populateOrderDetails(orders)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Observer: orderDetails is null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe loading state
|
||||||
|
viewModel.isLoading.observe(this) { isLoading ->
|
||||||
|
Log.d(TAG, "Observer: isLoading=$isLoading")
|
||||||
|
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe error messages
|
||||||
|
viewModel.error.observe(this) { errorMsg ->
|
||||||
|
if (!errorMsg.isNullOrEmpty()) {
|
||||||
|
Log.e(TAG, "Observer: Error received: $errorMsg")
|
||||||
|
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe success status
|
||||||
|
viewModel.isSuccess.observe(this) { isSuccess ->
|
||||||
|
Log.d(TAG, "Observer: isSuccess=$isSuccess")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe messages
|
||||||
|
viewModel.message.observe(this) { message ->
|
||||||
|
if (!message.isNullOrEmpty()) {
|
||||||
|
Log.d(TAG, "Observer: Message: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadOrderDetails() {
|
||||||
|
Log.d(TAG, "loadOrderDetails: Requesting order details for orderId=$orderId")
|
||||||
|
viewModel.getOrderDetails(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populateOrderDetails(orders: Orders) {
|
||||||
|
Log.d(TAG, "populateOrderDetails: Populating UI with order data")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Set order date and payment deadline
|
||||||
|
binding.tvOrderDate.text = formatDate(orders.createdAt)
|
||||||
|
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt)
|
||||||
|
|
||||||
|
Log.d(TAG, "populateOrderDetails: Order created at ${orders.createdAt}, formatted as ${binding.tvOrderDate.text}")
|
||||||
|
|
||||||
|
// Set address information
|
||||||
|
binding.tvRecipientName.text = orders.detail
|
||||||
|
binding.tvAddress.text = "${orders.street}, ${orders.subdistrict}"
|
||||||
|
|
||||||
|
Log.d(TAG, "populateOrderDetails: Shipping to ${orders.detail} at ${orders.street}")
|
||||||
|
|
||||||
|
// Set courier info
|
||||||
|
binding.tvCourier.text = "${orders.courier} ${orders.service}"
|
||||||
|
|
||||||
|
Log.d(TAG, "populateOrderDetails: Courier=${orders.courier}, Service=${orders.service}")
|
||||||
|
|
||||||
|
// Set product details using RecyclerView
|
||||||
|
Log.d(TAG, "populateOrderDetails: Setting up products RecyclerView with ${orders.orderItems.size} items")
|
||||||
|
setupProductsRecyclerView(orders.orderItems)
|
||||||
|
|
||||||
|
// Set payment method
|
||||||
|
binding.tvPaymentMethod.text = "Bank Transfer - ${orders.payInfoName ?: "BCA"}"
|
||||||
|
|
||||||
|
Log.d(TAG, "populateOrderDetails: Payment method=${orders.payInfoName ?: "BCA"}")
|
||||||
|
|
||||||
|
// Set subtotal, shipping cost, and total
|
||||||
|
val subtotal = orders.totalAmount?.toIntOrNull()?.minus(orders.shipmentPrice.toIntOrNull() ?: 0) ?: 0
|
||||||
|
binding.tvSubtotal.text = "Rp$subtotal"
|
||||||
|
binding.tvShippingCost.text = "Rp${orders.shipmentPrice}"
|
||||||
|
binding.tvTotal.text = "Rp${orders.totalAmount}"
|
||||||
|
|
||||||
|
Log.d(TAG, "populateOrderDetails: Subtotal=$subtotal, Shipping=${orders.shipmentPrice}, Total=${orders.totalAmount}")
|
||||||
|
|
||||||
|
// Adjust buttons based on order status
|
||||||
|
Log.d(TAG, "populateOrderDetails: Adjusting buttons for status=$orderStatus")
|
||||||
|
adjustButtonsBasedOnStatus(orders, orderStatus)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "populateOrderDetails: Error while populating UI", e)
|
||||||
|
Toast.makeText(this, "Error loading order details: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupProductsRecyclerView(orderItems: List<OrderListItemsItem>) {
|
||||||
|
Log.d(TAG, "setupProductsRecyclerView: Setting up RecyclerView with ${orderItems.size} items")
|
||||||
|
|
||||||
|
val adapter = DetailOrderItemsAdapter()
|
||||||
|
binding.rvOrderItems.apply {
|
||||||
|
layoutManager = LinearLayoutManager(this@DetailOrderStatusActivity)
|
||||||
|
this.adapter = adapter
|
||||||
|
}
|
||||||
|
adapter.submitList(orderItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adjustButtonsBasedOnStatus(orders: Orders, status: String) {
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Adjusting UI for status=$status")
|
||||||
|
|
||||||
|
// Reset button visibility first
|
||||||
|
binding.btnPrimary.visibility = View.GONE
|
||||||
|
binding.btnSecondary.visibility = View.GONE
|
||||||
|
|
||||||
|
// Set status header
|
||||||
|
val statusText = when(status) {
|
||||||
|
"pending" -> "Belum Bayar"
|
||||||
|
"unpaid" -> "Belum Bayar"
|
||||||
|
"processed" -> "Diproses"
|
||||||
|
"shipped" -> "Dikirim"
|
||||||
|
"delivered" -> "Diterima"
|
||||||
|
"completed" -> "Selesai"
|
||||||
|
"canceled" -> "Dibatalkan"
|
||||||
|
else -> "Detail Pesanan"
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvStatusHeader.text = statusText
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Status header set to '$statusText'")
|
||||||
|
|
||||||
|
when (status) {
|
||||||
|
"pending", "unpaid" -> {
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for pending/unpaid order")
|
||||||
|
|
||||||
|
// Show status note
|
||||||
|
binding.tvStatusNote.visibility = View.VISIBLE
|
||||||
|
binding.tvStatusNote.text = "Pesanan ini harus dibayar sebelum ${formatDatePay(orders.updatedAt)}"
|
||||||
|
|
||||||
|
// Set buttons
|
||||||
|
binding.btnSecondary.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "Batalkan Pesanan"
|
||||||
|
setOnClickListener {
|
||||||
|
Log.d(TAG, "Cancel Order button clicked")
|
||||||
|
showCancelOrderDialog(orders.orderId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnPrimary.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "Bayar Sekarang"
|
||||||
|
setOnClickListener {
|
||||||
|
Log.d(TAG, "Pay Now button clicked, navigating to PaymentActivity")
|
||||||
|
val intent = Intent(this@DetailOrderStatusActivity, PaymentActivity::class.java)
|
||||||
|
intent.putExtra("ORDER_ID", orders.orderId)
|
||||||
|
intent.putExtra("ORDER_PAYMENT_ID", orders.paymentInfoId)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"processed" -> {
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for processed order")
|
||||||
|
|
||||||
|
binding.tvStatusNote.visibility = View.VISIBLE
|
||||||
|
binding.tvStatusNote.text = "Penjual sedang memproses pesanan Anda"
|
||||||
|
|
||||||
|
binding.btnSecondary.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "Batalkan Pesanan"
|
||||||
|
setOnClickListener {
|
||||||
|
Log.d(TAG, "Cancel Order button clicked for processed order")
|
||||||
|
showCancelOrderDialog(orders.orderId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"shipped" -> {
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for shipped order")
|
||||||
|
|
||||||
|
binding.tvStatusNote.visibility = View.VISIBLE
|
||||||
|
binding.tvStatusNote.text = "Pesanan Anda sedang dalam perjalanan. Akan sampai sekitar ${formatShipmentDate(orders.updatedAt, orders.etd ?: "0")}"
|
||||||
|
|
||||||
|
binding.btnSecondary.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "Ajukan Komplain"
|
||||||
|
setOnClickListener {
|
||||||
|
Log.d(TAG, "Complaint button clicked")
|
||||||
|
showCancelOrderDialog(orders.orderId.toString()) // For now, reuse the cancel dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnPrimary.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "Terima Pesanan"
|
||||||
|
|
||||||
|
val completedOrderRequest = CompletedOrderRequest(
|
||||||
|
orderId = orders.orderId,
|
||||||
|
statusComplete = "completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
Log.d(TAG, "Confirm receipt button clicked, marking order as completed")
|
||||||
|
viewModel.confirmOrderCompleted(completedOrderRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"delivered", "completed" -> {
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for delivered/completed order")
|
||||||
|
|
||||||
|
binding.tvStatusNote.visibility = View.VISIBLE
|
||||||
|
binding.tvStatusNote.text = "Pesanan telah selesai"
|
||||||
|
|
||||||
|
binding.btnPrimary.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "Beri Ulasan"
|
||||||
|
setOnClickListener {
|
||||||
|
Log.d(TAG, "Review button clicked")
|
||||||
|
addReviewForOrder(orders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"canceled" -> {
|
||||||
|
Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for canceled order")
|
||||||
|
|
||||||
|
binding.tvStatusNote.visibility = View.VISIBLE
|
||||||
|
binding.tvStatusNote.text = "Pesanan dibatalkan: ${orders.cancelReason ?: "Alasan tidak diberikan"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addReviewForOrder(orders: Orders) {
|
||||||
|
Log.d(TAG, "addReviewForOrder: Preparing to add review for order ${orders.orderId}")
|
||||||
|
|
||||||
|
val orderItems = orders.orderItems
|
||||||
|
|
||||||
|
if (orderItems.isNotEmpty()) {
|
||||||
|
Log.d(TAG, "addReviewForOrder: Found ${orderItems.size} items to review")
|
||||||
|
|
||||||
|
// For single item review
|
||||||
|
if (orderItems.size == 1) {
|
||||||
|
val item = orderItems[0]
|
||||||
|
Log.d(TAG, "addReviewForOrder: Launching single item review for orderItemId=${item.orderItemId}")
|
||||||
|
|
||||||
|
val intent = Intent(this, CreateReviewActivity::class.java).apply {
|
||||||
|
putExtra("order_item_id", item.orderItemId)
|
||||||
|
putExtra("product_name", item.productName)
|
||||||
|
putExtra("product_image", item.productImage)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_REVIEW)
|
||||||
|
}
|
||||||
|
// For multiple items
|
||||||
|
else {
|
||||||
|
Log.d(TAG, "addReviewForOrder: Launching multi-item review with ${orderItems.size} items")
|
||||||
|
|
||||||
|
val reviewItems = orderItems.map { item ->
|
||||||
|
ReviewUIItem(
|
||||||
|
orderItemId = item.orderItemId,
|
||||||
|
productName = item.productName,
|
||||||
|
productImage = item.productImage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val itemsJson = Gson().toJson(reviewItems)
|
||||||
|
Log.d(TAG, "addReviewForOrder: JSON prepared for items: ${itemsJson.take(100)}...")
|
||||||
|
|
||||||
|
val intent = Intent(this, ReviewProductActivity::class.java).apply {
|
||||||
|
putExtra("order_items", itemsJson)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_REVIEW)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "addReviewForOrder: No items found to review")
|
||||||
|
Toast.makeText(this, "No items to review", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCancelOrderDialog(orderId: String) {
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Showing dialog for orderId=$orderId")
|
||||||
|
|
||||||
|
val dialog = Dialog(this)
|
||||||
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
dialog.setContentView(R.layout.dialog_cancel_order)
|
||||||
|
dialog.setCancelable(true)
|
||||||
|
|
||||||
|
// Store dialog reference
|
||||||
|
cancelDialog = dialog
|
||||||
|
|
||||||
|
// Set the dialog width to match parent
|
||||||
|
val window = dialog.window
|
||||||
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
// Get references to the views in the dialog
|
||||||
|
val spinnerCancelReason = dialog.findViewById<AutoCompleteTextView>(R.id.spinnerCancelReason)
|
||||||
|
val tilCancelReason = dialog.findViewById<TextInputLayout>(R.id.tilCancelReason)
|
||||||
|
val btnCancelDialog = dialog.findViewById<MaterialButton>(R.id.btnCancelDialog)
|
||||||
|
val btnConfirmCancel = dialog.findViewById<MaterialButton>(R.id.btnConfirmCancel)
|
||||||
|
val ivComplaintImage = dialog.findViewById<ImageView>(R.id.ivComplaintImage)
|
||||||
|
val tvSelectImage = dialog.findViewById<TextView>(R.id.tvSelectImage)
|
||||||
|
|
||||||
|
dialogImageView = ivComplaintImage
|
||||||
|
dialogSelectTextView = tvSelectImage
|
||||||
|
|
||||||
|
// Set up the reasons dropdown
|
||||||
|
val reasons = this.resources.getStringArray(R.array.cancellation_reasons)
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Setting up dropdown with ${reasons.size} reasons")
|
||||||
|
|
||||||
|
val adapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, reasons)
|
||||||
|
spinnerCancelReason.setAdapter(adapter)
|
||||||
|
|
||||||
|
// For storing the selected image URI
|
||||||
|
var selectedImageUri: Uri? = null
|
||||||
|
|
||||||
|
// Set click listener for image selection
|
||||||
|
ivComplaintImage.setOnClickListener {
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Image selection clicked")
|
||||||
|
|
||||||
|
// Create an intent to open the image picker
|
||||||
|
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||||
|
(this as? Activity)?.startActivityForResult(galleryIntent, REQUEST_IMAGE_PICK)
|
||||||
|
|
||||||
|
// Set up result handler in the activity
|
||||||
|
val activity = this as? Activity
|
||||||
|
activity?.let {
|
||||||
|
// Remove any existing callbacks to avoid memory leaks
|
||||||
|
if (imagePickCallback != null) {
|
||||||
|
imagePickCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new callback for this specific dialog
|
||||||
|
imagePickCallback = { uri ->
|
||||||
|
Log.d(TAG, "imagePickCallback: Image selected, URI=$uri")
|
||||||
|
selectedImageUri = uri
|
||||||
|
|
||||||
|
// Load and display the selected image
|
||||||
|
ivComplaintImage.setImageURI(uri)
|
||||||
|
tvSelectImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set click listeners for buttons
|
||||||
|
btnCancelDialog.setOnClickListener {
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Cancel button clicked, dismissing dialog")
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnConfirmCancel.setOnClickListener {
|
||||||
|
val reason = spinnerCancelReason.text.toString().trim()
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Confirm cancel clicked with reason: $reason")
|
||||||
|
|
||||||
|
if (reason.isEmpty()) {
|
||||||
|
Log.w(TAG, "showCancelOrderDialog: No reason selected")
|
||||||
|
tilCancelReason.error = this.getString(R.string.please_select_cancellation_reason)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear error if any
|
||||||
|
tilCancelReason.error = null
|
||||||
|
|
||||||
|
// Convert selected image to file if available
|
||||||
|
val imageFile = selectedImageUri?.let { uri ->
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Converting URI to file: $uri")
|
||||||
|
// Get the file path from URI
|
||||||
|
val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
|
||||||
|
val cursor = this.contentResolver.query(uri, filePathColumn, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val columnIndex = it.getColumnIndex(filePathColumn[0])
|
||||||
|
val filePath = it.getString(columnIndex)
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: File path: $filePath")
|
||||||
|
return@let File(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.w(TAG, "showCancelOrderDialog: Failed to get file path from URI")
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "showCancelOrderDialog: Error getting file from URI: ${e.message}", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Showing loading indicator")
|
||||||
|
val loadingView = View(this).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
setBackgroundColor(Color.parseColor("#80000000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.addContentView(loadingView, loadingView.layoutParams)
|
||||||
|
|
||||||
|
// Call the ViewModel to cancel the order with image
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Calling cancelOrderWithImage for orderId=$orderId")
|
||||||
|
viewModel.cancelOrderWithImage(orderId.toInt(), reason, imageFile)
|
||||||
|
|
||||||
|
// Observe for success/failure
|
||||||
|
viewModel.isSuccess.observe(this) { isSuccess ->
|
||||||
|
Log.d(TAG, "showCancelOrderDialog observer: isSuccess=$isSuccess")
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Order canceled successfully")
|
||||||
|
Toast.makeText(this, getString(R.string.order_canceled_successfully), Toast.LENGTH_SHORT).show()
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
// Set result and finish
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "showCancelOrderDialog: Failed to cancel order: ${viewModel.message.value}")
|
||||||
|
Toast.makeText(this, viewModel.message.value ?: getString(R.string.failed_to_cancel_order), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "showCancelOrderDialog: Dialog setup complete, showing dialog")
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(dateString: String): String {
|
||||||
|
Log.d(TAG, "formatDate: Formatting date: $dateString")
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
val outputFormat = SimpleDateFormat("HH:mm dd MMMM yyyy", Locale("id", "ID"))
|
||||||
|
|
||||||
|
val date = inputFormat.parse(dateString)
|
||||||
|
|
||||||
|
date?.let {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = it
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 23)
|
||||||
|
calendar.set(Calendar.MINUTE, 59)
|
||||||
|
|
||||||
|
val formatted = outputFormat.format(calendar.time)
|
||||||
|
Log.d(TAG, "formatDate: Formatted date: $formatted")
|
||||||
|
formatted
|
||||||
|
} ?: dateString
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "formatDate: Error formatting date: ${e.message}", e)
|
||||||
|
dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDatePay(dateString: String): String {
|
||||||
|
Log.d(TAG, "formatDatePay: Formatting payment date: $dateString")
|
||||||
|
|
||||||
|
return try {
|
||||||
|
// Parse the ISO 8601 date
|
||||||
|
val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
val createdDate = isoDateFormat.parse(dateString)
|
||||||
|
|
||||||
|
// Add 24 hours to get due date
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = createdDate
|
||||||
|
calendar.add(Calendar.HOUR, 24)
|
||||||
|
val dueDate = calendar.time
|
||||||
|
|
||||||
|
// Format due date for display
|
||||||
|
val dueDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
|
||||||
|
val formatted = dueDateFormat.format(calendar.time)
|
||||||
|
|
||||||
|
Log.d(TAG, "formatDatePay: Formatted payment date: $formatted")
|
||||||
|
formatted
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "formatDatePay: Error formatting date: ${e.message}", e)
|
||||||
|
dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatShipmentDate(dateString: String, estimateString: String): String {
|
||||||
|
Log.d(TAG, "formatShipmentDate: Formatting shipment date: $dateString with ETD: $estimateString")
|
||||||
|
|
||||||
|
return try {
|
||||||
|
// Safely parse the estimate to Int
|
||||||
|
val estimate = if (estimateString.isNullOrEmpty()) 0 else estimateString.toInt()
|
||||||
|
Log.d(TAG, "formatShipmentDate: Parsed ETD as $estimate days")
|
||||||
|
|
||||||
|
// Parse the input date
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
// Output format
|
||||||
|
val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale("id", "ID"))
|
||||||
|
|
||||||
|
// Parse the input date
|
||||||
|
val date = inputFormat.parse(dateString)
|
||||||
|
|
||||||
|
date?.let {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = it
|
||||||
|
|
||||||
|
// Add estimated days
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, estimate)
|
||||||
|
val formatted = outputFormat.format(calendar.time)
|
||||||
|
|
||||||
|
Log.d(TAG, "formatShipmentDate: Estimated arrival date: $formatted")
|
||||||
|
formatted
|
||||||
|
} ?: dateString
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "formatShipmentDate: Error formatting shipment date: ${e.message}", e)
|
||||||
|
dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode")
|
||||||
|
|
||||||
|
when (requestCode) {
|
||||||
|
REQUEST_IMAGE_PICK -> {
|
||||||
|
if (resultCode == RESULT_OK && data != null) {
|
||||||
|
// Get the selected image URI
|
||||||
|
selectedImageUri = data.data
|
||||||
|
Log.d(TAG, "onActivityResult: Image selected, URI=$selectedImageUri")
|
||||||
|
|
||||||
|
// Update the image view in the dialog if the dialog is still showing
|
||||||
|
if (cancelDialog?.isShowing == true) {
|
||||||
|
Log.d(TAG, "onActivityResult: Updating image in dialog")
|
||||||
|
dialogImageView?.setImageURI(selectedImageUri)
|
||||||
|
dialogSelectTextView?.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "onActivityResult: Dialog is not showing, cannot update image")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "onActivityResult: Image selection canceled or failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUEST_CODE_REVIEW -> {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
// Review submitted successfully
|
||||||
|
Log.d(TAG, "onActivityResult: Review submitted successfully")
|
||||||
|
Toast.makeText(this, "Review submitted successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Refresh order details
|
||||||
|
loadOrderDetails()
|
||||||
|
|
||||||
|
// Set result to notify parent activity
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "onActivityResult: Review submission canceled or failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.d(TAG, "onDestroy: Cleaning up references")
|
||||||
|
super.onDestroy()
|
||||||
|
// Clean up references
|
||||||
|
cancelDialog = null
|
||||||
|
dialogImageView = null
|
||||||
|
dialogSelectTextView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REQUEST_IMAGE_PICK = 100
|
||||||
|
private const val REQUEST_CODE_REVIEW = 101
|
||||||
|
private const val TAG = "DetailOrderActivity" // Add tag for logging
|
||||||
|
|
||||||
|
|
||||||
|
private var imagePickCallback: ((Uri) -> Unit)? = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history.detailorder
|
||||||
|
|
||||||
|
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.CompletedOrderRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.order.Orders
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class DetailOrderViewModel(private val orderRepository: OrderRepository): ViewModel() {
|
||||||
|
|
||||||
|
private val _orderDetails = MutableLiveData<Orders>()
|
||||||
|
val orderDetails: LiveData<Orders> = _orderDetails
|
||||||
|
|
||||||
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
|
|
||||||
|
private val _error = MutableLiveData<String>()
|
||||||
|
val error: LiveData<String> = _error
|
||||||
|
|
||||||
|
private val _isSuccess = MutableLiveData<Boolean>()
|
||||||
|
val isSuccess: LiveData<Boolean> = _isSuccess
|
||||||
|
|
||||||
|
private val _message = MutableLiveData<String>()
|
||||||
|
val message: LiveData<String> = _message
|
||||||
|
|
||||||
|
fun getOrderDetails(orderId: Int) {
|
||||||
|
_isLoading.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val response = orderRepository.getOrderDetails(orderId)
|
||||||
|
if (response != null) {
|
||||||
|
_orderDetails.value = response.orders
|
||||||
|
} else {
|
||||||
|
_error.value = "Failed to load order details"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_error.value = "Error: ${e.message}"
|
||||||
|
Log.e("DetailOrderViewModel", "Error loading order details", e)
|
||||||
|
} finally {
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirmOrderCompleted(detailOrderRequest: CompletedOrderRequest) {
|
||||||
|
_isLoading.value = true
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
orderRepository.confirmOrderCompleted(detailOrderRequest)
|
||||||
|
_isSuccess.value = true
|
||||||
|
_message.value = "Order status updated successfully"
|
||||||
|
|
||||||
|
getOrderDetails(detailOrderRequest.orderId)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_isSuccess.value = false
|
||||||
|
_message.value = "Error: ${e.message}"
|
||||||
|
Log.e("DetailOrderViewModel", "Error updating order status", e)
|
||||||
|
} finally {
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelOrderWithImage(orderId: Int, reason: String, imageFile: File?) {
|
||||||
|
_isLoading.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
orderRepository.submitComplaint(orderId.toString(), reason, imageFile)
|
||||||
|
_isSuccess.value = true
|
||||||
|
_message.value = "Order canceled successfully"
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_isSuccess.value = false
|
||||||
|
_message.value = "Error: ${e.message}"
|
||||||
|
Log.e("DetailOrderViewModel", "Error canceling order", e)
|
||||||
|
} finally {
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
429
app/src/main/res/layout/activity_detail_order_status.xml
Normal file
429
app/src/main/res/layout/activity_detail_order_status.xml
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
<?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.history.detailorder.DetailOrderStatusActivity">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/header"
|
||||||
|
layout="@layout/header"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scrollViewDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/buttonLayout"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/header">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Status header -->
|
||||||
|
<!-- Order Status Card -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStatusHeader"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/blue_500"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:text="Belum Bayar"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- Status Note (if any) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStatusNote"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:text="Pesanan ini harus dibayar sebelum 18 November 2024"
|
||||||
|
android:textColor="@color/blue_400"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="visible"/>
|
||||||
|
|
||||||
|
<!-- Order dates -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvOrderDateLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Tanggal Pesanan:"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvOrderDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="15 November 2024"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tvOrderDateLabel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvOrderDateLabel" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPaymentDeadlineLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Batas Pembayaran:"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvOrderDateLabel"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPaymentDeadline"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="18 November 2024"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tvPaymentDeadlineLabel"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvOrderDate" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:background="@color/light_gray" />
|
||||||
|
|
||||||
|
<!-- Shipping Information -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Informasi Pengiriman"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
android:textSize="16sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="Alamat"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRecipientName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Gracia 081234533453"
|
||||||
|
android:fontFamily="@font/dmsans_italic"
|
||||||
|
android:textColor="@color/black_400"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAddress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Jl. Pegangsaan Timur"
|
||||||
|
android:fontFamily="@font/dmsans_italic"
|
||||||
|
android:textColor="@color/black_400"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Kurir"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCourier"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="JNE Reguler"
|
||||||
|
android:fontFamily="@font/dmsans_italic"
|
||||||
|
android:textColor="@color/black_400"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:background="@color/light_gray" />
|
||||||
|
|
||||||
|
<!-- Order Items -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStoreName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="SnackEnak"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvOrderItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:itemCount="2"
|
||||||
|
tools:listitem="@layout/item_order_detail_product" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Payment Method Card -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Metode Pembayaran"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPaymentMethod"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Bank Transfer - Bank BCA"
|
||||||
|
android:textColor="@color/black_400"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Order Summary Card -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Subtotal"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="1 item"
|
||||||
|
android:textColor="@color/black_400"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSubtotal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rp65.000"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Biaya Pengiriman"
|
||||||
|
android:textColor="@color/black_400"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvShippingCost"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rp15.000"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@color/light_gray" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Total"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rp75.000"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Add space at the bottom for buttons -->
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<!-- Buttons at the bottom -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/buttonLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/scrollViewDetail">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnSecondary"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Batalkan Pesanan"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnPrimary"
|
||||||
|
style="@style/Widget.MaterialComponents.Button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Bayar Sekarang"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
61
app/src/main/res/layout/item_order_detail_product.xml
Normal file
61
app/src/main/res/layout/item_order_detail_product.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivProduct"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="Keripik Balado"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ivProduct"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/ivProduct" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQuantity"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="1 buah"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
|
android:textColor="@color/black_200"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ivProduct"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvProductName" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPrice"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Rp65.000"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ivProduct"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvQuantity" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Reference in New Issue
Block a user