diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3c3dfbd..8e9559d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,9 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt
index b8d9c36..dd6e5c1 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt
@@ -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()
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt
index 45c3297..c420ae8 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt
@@ -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())
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt
index 05d1e78..fcc9837 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt
@@ -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() {
}
}
}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt
new file mode 100644
index 0000000..f04cb6d
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt
@@ -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() {
+
+ private val items = mutableListOf()
+
+ fun submitList(newItems: List) {
+ 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}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt
new file mode 100644
index 0000000..414e013
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt
@@ -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()
+ 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) {
+ 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(R.id.spinnerCancelReason)
+ val tilCancelReason = dialog.findViewById(R.id.tilCancelReason)
+ val btnCancelDialog = dialog.findViewById(R.id.btnCancelDialog)
+ val btnConfirmCancel = dialog.findViewById(R.id.btnConfirmCancel)
+ val ivComplaintImage = dialog.findViewById(R.id.ivComplaintImage)
+ val tvSelectImage = dialog.findViewById(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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderViewModel.kt
new file mode 100644
index 0000000..7bfaea0
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderViewModel.kt
@@ -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()
+ val orderDetails: LiveData = _orderDetails
+
+ private val _isLoading = MutableLiveData()
+ val isLoading: LiveData = _isLoading
+
+ private val _error = MutableLiveData()
+ val error: LiveData = _error
+
+ private val _isSuccess = MutableLiveData()
+ val isSuccess: LiveData = _isSuccess
+
+ private val _message = MutableLiveData()
+ val message: LiveData = _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
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_detail_order_status.xml b/app/src/main/res/layout/activity_detail_order_status.xml
new file mode 100644
index 0000000..c650212
--- /dev/null
+++ b/app/src/main/res/layout/activity_detail_order_status.xml
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_order_detail_product.xml b/app/src/main/res/layout/item_order_detail_product.xml
new file mode 100644
index 0000000..258b0ad
--- /dev/null
+++ b/app/src/main/res/layout/item_order_detail_product.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file