update detail order

This commit is contained in:
shaulascr
2025-05-13 17:01:52 +07:00
parent edddecaaf0
commit 79c32fc5ee
9 changed files with 1357 additions and 4 deletions

View File

@ -29,6 +29,9 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".ui.order.history.detailorder.DetailOrderStatusActivity"
android:exported="false" />
<activity
android:name=".ui.order.review.CreateReviewActivity"
android:exported="false" />

View File

@ -7,9 +7,13 @@ import android.util.Log
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
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 androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
@ -47,6 +51,22 @@ class CheckoutActivity : AppCompatActivity() {
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
setupToolbar()
setupObservers()

View File

@ -329,7 +329,6 @@ class OrderHistoryAdapter(
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())

View File

@ -1,10 +1,13 @@
package com.alya.ecommerce_serang.ui.order.history
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
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.databinding.FragmentOrderListBinding
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.SessionManager
@ -121,10 +125,21 @@ class OrderListFragment : Fragment() {
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) {
// In a real app, you would navigate to order detail screen
// For example: findNavController().navigate(OrderListFragmentDirections.actionToOrderDetail(order.orderId))
Toast.makeText(requireContext(), "Order ID: ${order.orderId}", Toast.LENGTH_SHORT).show()
val intent = Intent(requireContext(), DetailOrderStatusActivity::class.java).apply {
putExtra("ORDER_ID", order.orderId)
putExtra("ORDER_STATUS", status) // Pass the current status
}
detailOrderLauncher.launch(intent)
}
override fun onDestroyView() {
@ -148,4 +163,5 @@ class OrderListFragment : Fragment() {
}
}
}
}

View File

@ -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}"
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}
}

View 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>

View 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>