add sent evidence payment FIX

This commit is contained in:
shaulascr
2025-04-19 19:13:22 +07:00
parent d1fcec6d14
commit 2db0d2b27d
12 changed files with 737 additions and 55 deletions

View File

@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -19,6 +21,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.detail.AddEvidencePaymentActivity"
android:exported="false" />
<activity <activity
android:name=".ui.order.history.HistoryActivity" android:name=".ui.order.history.HistoryActivity"
android:exported="false" /> android:exported="false" />

View File

@ -0,0 +1,10 @@
package com.alya.ecommerce_serang.data.api.dto
import okhttp3.MultipartBody
import okhttp3.RequestBody
data class AddEvidenceMultipartRequest(
val orderId: RequestBody,
val amount: RequestBody,
val evidence: MultipartBody.Part
)

View File

@ -1,7 +1,7 @@
package com.alya.ecommerce_serang.data.api.dto package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import retrofit2.http.Multipart import okhttp3.MultipartBody
data class AddEvidenceRequest ( data class AddEvidenceRequest (
@SerializedName("orer_id") @SerializedName("orer_id")
@ -11,5 +11,5 @@ data class AddEvidenceRequest (
val amount : String, val amount : String,
@SerializedName("evidence") @SerializedName("evidence")
val evidence: Multipart val evidence: MultipartBody.Part
) )

View File

@ -33,14 +33,18 @@ import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.PUT import retrofit2.http.PUT
import retrofit2.http.Part
import retrofit2.http.Path import retrofit2.http.Path
interface ApiService { interface ApiService {
@ -99,6 +103,14 @@ interface ApiService {
@Body request : AddEvidenceRequest, @Body request : AddEvidenceRequest,
): Response<AddEvidenceResponse> ): Response<AddEvidenceResponse>
@Multipart
@POST("order/addevidence")
suspend fun addEvidenceMultipart(
@Part("order_id") orderId: RequestBody,
@Part("amount") amount: RequestBody,
@Part evidence: MultipartBody.Part
): Response<AddEvidenceResponse>
@GET("order/{status}") @GET("order/{status}")
suspend fun getOrderList( suspend fun getOrderList(
@Path("status") status: String @Path("status") status: String

View File

@ -1,7 +1,7 @@
package com.alya.ecommerce_serang.data.repository package com.alya.ecommerce_serang.data.repository
import android.util.Log import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequest
@ -246,30 +246,59 @@ class OrderRepository(private val apiService: ApiService) {
} }
} }
suspend fun uploadPaymentProof(request : AddEvidenceRequest): Result<AddEvidenceResponse> { // suspend fun uploadPaymentProof(request : AddEvidenceRequest): Result<AddEvidenceResponse> {
return try { // return try {
Log.d("OrderRepository", "Add Evidence : $request") // Log.d("OrderRepository", "Add Evidence : $request")
val response = apiService.addEvidence(request) // val response = apiService.addEvidence(request)
//
// if (response.isSuccessful) {
// val addEvidenceResponse = response.body()
// if (addEvidenceResponse != null) {
// Log.d("OrderRepository", "Add Evidence successfully: ${addEvidenceResponse.message}")
// Result.Success(addEvidenceResponse)
// } else {
// Log.e("OrderRepository", "Response body was null")
// Result.Error(Exception("Empty response from server"))
// }
// } else {
// val errorBody = response.errorBody()?.string() ?: "Unknown error"
// Log.e("OrderRepository", "Error Add Evidence : $errorBody")
// Result.Error(Exception(errorBody))
// }
// } catch (e: Exception) {
// Log.e("OrderRepository", "Exception Add Evidence ", e)
// Result.Error(e)
// }
// }
suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> {
return try {
Log.d("OrderRepository", "Uploading payment proof...")
if (response.isSuccessful) { val response = apiService.addEvidenceMultipart(
val addEvidenceResponse = response.body() orderId = request.orderId,
if (addEvidenceResponse != null) { amount = request.amount,
Log.d("OrderRepository", "Add Evidence successfully: ${addEvidenceResponse.message}") evidence = request.evidence
Result.Success(addEvidenceResponse) )
} else {
Log.e("OrderRepository", "Response body was null") if (response.isSuccessful) {
Result.Error(Exception("Empty response from server")) val addEvidenceResponse = response.body()
} if (addEvidenceResponse != null) {
Log.d("OrderRepository", "Payment proof uploaded successfully: ${addEvidenceResponse.message}")
Result.Success(addEvidenceResponse)
} else { } else {
val errorBody = response.errorBody()?.string() ?: "Unknown error" Log.e("OrderRepository", "Response body was null")
Log.e("OrderRepository", "Error Add Evidence : $errorBody") Result.Error(Exception("Empty response from server"))
Result.Error(Exception(errorBody))
} }
} catch (e: Exception) { } else {
Log.e("OrderRepository", "Exception Add Evidence ", e) val errorBody = response.errorBody()?.string() ?: "Unknown error"
Result.Error(e) Log.e("OrderRepository", "Error uploading payment proof: $errorBody")
Result.Error(Exception(errorBody))
} }
} catch (e: Exception) {
Log.e("OrderRepository", "Exception uploading payment proof", e)
Result.Error(e)
} }
}
suspend fun getOrderList(status: String): Result<OrderListResponse> { suspend fun getOrderList(status: String): Result<OrderListResponse> {
return try { return try {

View File

@ -0,0 +1,340 @@
package com.alya.ecommerce_serang.ui.order.detail
import android.Manifest
import android.R
import android.app.DatePickerDialog
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.webkit.MimeTypeMap
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.databinding.ActivityAddEvidencePaymentBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class AddEvidencePaymentActivity : AppCompatActivity() {
private lateinit var binding: ActivityAddEvidencePaymentBinding
private lateinit var sessionManager: SessionManager
private var orderId: Int = 0
private var paymentInfoId: Int = 0
private lateinit var productPrice: String
private var selectedImageUri: Uri? = null
private val viewModel: PaymentViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val orderRepository = OrderRepository(apiService)
PaymentViewModel(orderRepository)
}
}
private val paymentMethods = arrayOf(
"Pilih metode pembayaran",
"Transfer Bank",
"E-Wallet",
"Virtual Account",
"Cash on Delivery"
)
private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
selectedImageUri = it
binding.ivUploadedImage.setImageURI(selectedImageUri)
binding.ivUploadedImage.visibility = View.VISIBLE
binding.layoutUploadPlaceholder.visibility = View.GONE
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddEvidencePaymentBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
intent.extras?.let { bundle ->
orderId = bundle.getInt("ORDER_ID", 0)
paymentInfoId = bundle.getInt("PAYMENT_INFO_ID", 0)
productPrice = intent.getStringExtra("TOTAL_AMOUNT") ?: "Rp0"
}
setupUI()
viewModel.getOrderDetails(orderId)
setupListeners()
setupObservers()
}
private fun setupUI() {
// Set product details\
// Setup payment methods spinner
val adapter = ArrayAdapter(this, R.layout.simple_spinner_item, paymentMethods)
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
binding.spinnerPaymentMethod.adapter = adapter
}
private fun setupListeners() {
// Upload image button
binding.tvAddPhoto.setOnClickListener {
checkPermissionAndPickImage()
}
binding.frameUploadImage.setOnClickListener {
checkPermissionAndPickImage()
}
// Date picker
binding.tvPaymentDate.setOnClickListener {
showDatePicker()
}
// Payment method spinner
binding.spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// Skip the hint (first item)
if (position > 0) {
val selectedMethod = paymentMethods[position]
// You can also use it for further processing
Log.d(TAG, "Selected payment method: $selectedMethod")
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
// Do nothing
}
}
// Submit button
binding.btnSubmit.setOnClickListener {
validateAndUpload()
}
}
private fun setupObservers() {
viewModel.uploadResult.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
Toast.makeText(this, "Bukti pembayaran berhasil dikirim", Toast.LENGTH_SHORT).show()
Log.d(TAG, "Upload successful: ${result.data}")
// Navigate back or to confirmation screen
finish()
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e(TAG, "Upload failed: ${result.exception.message}")
Toast.makeText(this, "Gagal mengirim bukti pembayaran: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
is Result.Loading -> {
// Show loading indicator if needed
Log.d(TAG, "Uploading payment proof...")
}
}
}
}
private fun validateAndUpload() {
// Validate all fields
if (selectedImageUri == null) {
Toast.makeText(this, "Silahkan pilih bukti pembayaran", Toast.LENGTH_SHORT).show()
return
}
if (binding.spinnerPaymentMethod.selectedItemPosition == 0) {
Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
return
}
if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show()
return
}
if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()
return
}
// All validations passed, proceed with upload
uploadPaymentProof()
}
private fun uploadPaymentProof() {
selectedImageUri?.let { uri ->
// Convert URI to File
val file = getFileFromUri(uri)
file?.let {
try {
// Create MultipartBody.Part from File
val requestFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
val evidencePart = MultipartBody.Part.createFormData("evidence", file.name, requestFile)
// Create RequestBody for order ID and amount
val orderIdPart = orderId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
// Clean up the price string to get only the numeric value
val amountPart = productPrice.replace("Rp", "").replace(".", "").trim()
.toRequestBody("text/plain".toMediaTypeOrNull())
// Create the request object with the parts we need
val request = AddEvidenceMultipartRequest(
orderId = orderIdPart,
amount = amountPart,
evidence = evidencePart
)
// Log request details for debugging
Log.d(TAG, "Uploading payment proof - OrderID: $orderId, Amount: ${productPrice.replace("Rp", "").replace(".", "").trim()}")
Log.d(TAG, "File details - Name: ${file.name}, Size: ${file.length()} bytes, MIME: image/jpeg")
// Call the viewModel method
viewModel.uploadPaymentProof(request)
} catch (e: Exception) {
Log.e(TAG, "Error creating upload request: ${e.message}", e)
Toast.makeText(this, "Error preparing upload: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun getFileFromUri(uri: Uri): File? {
val contentResolver = applicationContext.contentResolver
val mimeType = contentResolver.getType(uri) ?: "image/jpeg"
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
Log.d("UploadEvidence", "URI: $uri")
Log.d("UploadEvidence", "Detected MIME type: $mimeType, extension: $extension")
// Ensure it's an image (either PNG, JPG, or JPEG)
if (mimeType != "image/png" && mimeType != "image/jpeg" && mimeType != "image/jpg") {
Log.e("UploadEvidence", "Invalid image MIME type: $mimeType. Only images are allowed.")
Toast.makeText(applicationContext, "Only image files are allowed", Toast.LENGTH_SHORT).show()
return null
}
try {
val inputStream = contentResolver.openInputStream(uri)
if (inputStream == null) {
Log.e("UploadEvidence", "Failed to open input stream from URI: $uri")
return null
}
// Create a temporary file with the correct extension
val tempFile = File.createTempFile("evidence_", ".$extension", cacheDir)
Log.d("UploadEvidence", "Temp file created at: ${tempFile.absolutePath}")
// Copy the content from inputStream to the temporary file
tempFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
inputStream.close()
}
// Verify if the file is a valid image
val bitmap = BitmapFactory.decodeFile(tempFile.absolutePath)
if (bitmap == null) {
Log.e("UploadEvidence", "File is not a valid image!")
tempFile.delete()
return null
} else {
bitmap.recycle() // Free memory
Log.d("UploadEvidence", "Valid image detected.")
}
Log.d("UploadEvidence", "File copied successfully. Size: ${tempFile.length()} bytes")
return tempFile
} catch (e: Exception) {
Log.e("UploadEvidence", "Error processing file: ${e.message}", e)
Toast.makeText(applicationContext, "Error processing image: ${e.message}", Toast.LENGTH_SHORT).show()
return null
}
}
private fun checkPermissionAndPickImage() {
val permission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_CODE_STORAGE_PERMISSION)
} else {
pickImage()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_STORAGE_PERMISSION && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
pickImage()
} else {
Toast.makeText(this, "Izin dibutuhkan untuk memilih gambar", Toast.LENGTH_SHORT).show()
}
}
private fun pickImage() {
getContent.launch("image/*")
}
private fun showDatePicker() {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
DatePickerDialog(
this,
{ _, selectedYear, selectedMonth, selectedDay ->
calendar.set(selectedYear, selectedMonth, selectedDay)
val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
binding.tvPaymentDate.text = sdf.format(calendar.time)
},
year, month, day
).show()
}
companion object {
private const val PERMISSION_REQUEST_CODE = 100
private const val TAG = "AddEvidenceActivity"
private const val REQUEST_CODE_STORAGE_PERMISSION = 100
}
}

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.ui.order.detail package com.alya.ecommerce_serang.ui.order.detail
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
@ -14,6 +15,7 @@ import com.alya.ecommerce_serang.utils.SessionManager
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
import java.util.TimeZone
class PaymentActivity : AppCompatActivity() { class PaymentActivity : AppCompatActivity() {
private lateinit var binding: ActivityPaymentBinding private lateinit var binding: ActivityPaymentBinding
@ -64,11 +66,12 @@ class PaymentActivity : AppCompatActivity() {
// Setup button upload bukti bayar // Setup button upload bukti bayar
binding.btnUploadPaymentProof.setOnClickListener { binding.btnUploadPaymentProof.setOnClickListener {
// Intent ke activity upload bukti bayar // Intent ke activity upload bukti bayar
// val intent = Intent(this, UploadPaymentProofActivity::class.java) val intent = Intent(this, AddEvidencePaymentActivity::class.java)
intent.putExtra("ORDER_ID", orderId) intent.putExtra("ORDER_ID", orderId)
intent.putExtra("PAYMENT_INFO_ID", paymentInfoId) intent.putExtra("PAYMENT_INFO_ID", paymentInfoId)
Log.d(TAG, "Received Order ID: $orderId, Payment Info ID: $paymentInfoId") intent.putExtra("TOTAL_AMOUNT", binding.tvTotalAmount.text.toString())
Log.d(TAG, "Received Order ID: $orderId, Payment Info ID: $paymentInfoId, Total Amount: ${binding.tvTotalAmount.text}")
startActivity(intent) startActivity(intent)
} }
@ -127,9 +130,10 @@ class PaymentActivity : AppCompatActivity() {
Log.d(TAG, "Setting up payment due date from updated at: $createdAt") Log.d(TAG, "Setting up payment due date from updated at: $createdAt")
try { try {
// Parse the created date // Parse the ISO 8601 date
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
val createdDate = dateFormat.parse(createdAt) ?: return isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
val createdDate = isoDateFormat.parse(createdAt) ?: return
// Add 24 hours to get due date // Add 24 hours to get due date
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
@ -139,10 +143,9 @@ class PaymentActivity : AppCompatActivity() {
// Format due date for display // Format due date for display
val dueDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()) val dueDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
binding.tvDueDate.text = "Jatuh tempo ${dueDateFormat.format(dueDate)}" binding.tvDueDate.text = "Jatuh tempo: ${dueDateFormat.format(dueDate)}"
Log.d(TAG, "Due Date: ${dueDateFormat.format(dueDate)}") Log.d(TAG, "Due Date: ${dueDateFormat.format(dueDate)}")
// Calculate remaining time // Calculate remaining time
val now = Calendar.getInstance().time val now = Calendar.getInstance().time
val diff = dueDate.time - now.time val diff = dueDate.time - now.time
@ -152,12 +155,12 @@ class PaymentActivity : AppCompatActivity() {
val minutes = (diff % (60 * 60 * 1000)) / (60 * 1000) val minutes = (diff % (60 * 60 * 1000)) / (60 * 1000)
binding.tvRemainingTime.text = "$hours jam $minutes menit" binding.tvRemainingTime.text = "$hours jam $minutes menit"
Log.d(TAG, "Remaining Time: $hours hours $minutes minutes") Log.d(TAG, "Remaining Time: $hours hours $minutes minutes")
} else { } else {
binding.tvRemainingTime.text = "Waktu habis" binding.tvRemainingTime.text = "Waktu habis"
} }
} catch (e: Exception) { } catch (e: Exception) {
binding.tvDueDate.text = "Jatuh tempo -" Log.e(TAG, "Error parsing date", e)
binding.tvDueDate.text = "Jatuh tempo: -"
binding.tvRemainingTime.text = "-" binding.tvRemainingTime.text = "-"
} }
} }

View File

@ -5,9 +5,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
import com.alya.ecommerce_serang.data.api.response.order.OrderListItemsItem import com.alya.ecommerce_serang.data.api.response.order.OrderListItemsItem
import com.alya.ecommerce_serang.data.api.response.order.Orders import com.alya.ecommerce_serang.data.api.response.order.Orders
import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class PaymentViewModel(private val repository: OrderRepository) : ViewModel() { class PaymentViewModel(private val repository: OrderRepository) : ViewModel() {
@ -31,6 +34,9 @@ class PaymentViewModel(private val repository: OrderRepository) : ViewModel() {
private val _error = MutableLiveData<String>() private val _error = MutableLiveData<String>()
val error: LiveData<String> get() = _error val error: LiveData<String> get() = _error
private val _uploadResult = MutableLiveData<Result<AddEvidenceResponse>>()
val uploadResult: LiveData<com.alya.ecommerce_serang.data.repository.Result<AddEvidenceResponse>> = _uploadResult
fun getOrderDetails(orderId: Int) { fun getOrderDetails(orderId: Int) {
_isLoading.value = true _isLoading.value = true
viewModelScope.launch { viewModelScope.launch {
@ -50,4 +56,17 @@ class PaymentViewModel(private val repository: OrderRepository) : ViewModel() {
} }
} }
} }
fun uploadPaymentProof(request: AddEvidenceMultipartRequest) {
viewModelScope.launch {
_uploadResult.value = com.alya.ecommerce_serang.data.repository.Result.Loading
try {
val result = repository.uploadPaymentProof(request)
_uploadResult.value = result
} catch (e: Exception) {
Log.e("PaymentProofViewModel", "Error uploading payment proof", e)
_uploadResult.value = com.alya.ecommerce_serang.data.repository.Result.Error(e)
}
}
}
} }

View File

@ -12,6 +12,10 @@ import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.order.OrdersItem import com.alya.ecommerce_serang.data.api.response.order.OrdersItem
import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
class OrderHistoryAdapter( class OrderHistoryAdapter(
private val onOrderClickListener: (OrdersItem) -> Unit private val onOrderClickListener: (OrdersItem) -> Unit
@ -48,7 +52,7 @@ class OrderHistoryAdapter(
private val tvShowMore: TextView = itemView.findViewById(R.id.tvShowMore) private val tvShowMore: TextView = itemView.findViewById(R.id.tvShowMore)
private val tvTotalAmount: TextView = itemView.findViewById(R.id.tvTotalAmount) private val tvTotalAmount: TextView = itemView.findViewById(R.id.tvTotalAmount)
private val tvItemCountLabel: TextView = itemView.findViewById(R.id.tv_count_total_item) private val tvItemCountLabel: TextView = itemView.findViewById(R.id.tv_count_total_item)
private val tvDeadlineDate: TextView = itemView.findViewById(R.id.tvDeadlineDate) // private val tvDeadlineDate: TextView = itemView.findViewById(R.id.tvDeadlineDate)
fun bind(order: OrdersItem) { fun bind(order: OrdersItem) {
// Get store name from the first order item // Get store name from the first order item
@ -63,7 +67,7 @@ class OrderHistoryAdapter(
tvItemCountLabel.text = itemView.context.getString(R.string.item_count_prod, itemCount) tvItemCountLabel.text = itemView.context.getString(R.string.item_count_prod, itemCount)
// Set deadline date, adjust to each status // Set deadline date, adjust to each status
tvDeadlineDate.text = formatDate(order.createdAt) // tvDeadlineDate.text = formatDate(order.updatedAt)
// Set up the order items RecyclerView // Set up the order items RecyclerView
val productAdapter = OrderProductAdapter() val productAdapter = OrderProductAdapter()
@ -98,20 +102,6 @@ class OrderHistoryAdapter(
} }
private fun getStatusLabel(status: String): String {
return when (status.toLowerCase()) {
"pending" -> itemView.context.getString(R.string.pending_orders)
"unpaid" -> itemView.context.getString(R.string.unpaid_orders)
"processed" -> itemView.context.getString(R.string.processed_orders)
"paid" -> itemView.context.getString(R.string.paid_orders)
"shipped" -> itemView.context.getString(R.string.shipped_orders)
"delivered" -> itemView.context.getString(R.string.delivered_orders)
"completed" -> itemView.context.getString(R.string.completed_orders)
"canceled" -> itemView.context.getString(R.string.canceled_orders)
else -> status
}
}
private fun adjustButtonsAndText(status: String, order: OrdersItem) { private fun adjustButtonsAndText(status: String, order: OrdersItem) {
Log.d("OrderHistoryAdapter", "Adjusting buttons for status: $status") Log.d("OrderHistoryAdapter", "Adjusting buttons for status: $status")
// Mendapatkan referensi ke tombol-tombol // Mendapatkan referensi ke tombol-tombol
@ -119,6 +109,7 @@ class OrderHistoryAdapter(
val btnRight = itemView.findViewById<MaterialButton>(R.id.btn_right) val btnRight = itemView.findViewById<MaterialButton>(R.id.btn_right)
val statusOrder = itemView.findViewById<TextView>(R.id.tvOrderStatus) val statusOrder = itemView.findViewById<TextView>(R.id.tvOrderStatus)
val deadlineLabel = itemView.findViewById<TextView>(R.id.tvDeadlineLabel) val deadlineLabel = itemView.findViewById<TextView>(R.id.tvDeadlineLabel)
val deadlineDate = itemView.findViewById<TextView>(R.id.tvDeadlineDate)
// Reset visibility // Reset visibility
btnLeft.visibility = View.GONE btnLeft.visibility = View.GONE
@ -136,6 +127,10 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE visibility = View.VISIBLE
text = itemView.context.getString(R.string.dl_pending) text = itemView.context.getString(R.string.dl_pending)
} }
deadlineDate.apply {
visibility = View.VISIBLE
text = formatDate(order.createdAt)
}
} }
"unpaid" -> { "unpaid" -> {
statusOrder.apply { statusOrder.apply {
@ -152,6 +147,7 @@ class OrderHistoryAdapter(
setOnClickListener { setOnClickListener {
} }
} }
btnRight.apply { btnRight.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
text = itemView.context.getString(R.string.sent_evidence) text = itemView.context.getString(R.string.sent_evidence)
@ -165,6 +161,10 @@ class OrderHistoryAdapter(
itemView.context.startActivity(intent) itemView.context.startActivity(intent)
} }
} }
deadlineDate.apply {
visibility = View.VISIBLE
text = formatDatePay(order.updatedAt)
}
} }
"processed" -> { "processed" -> {
// Untuk status processed, tampilkan "Hubungi Penjual" // Untuk status processed, tampilkan "Hubungi Penjual"
@ -202,6 +202,10 @@ class OrderHistoryAdapter(
// Handle click event // Handle click event
} }
} }
deadlineDate.apply {
visibility = View.VISIBLE
text = formatShipmentDate(order.updatedAt, order.etd.toInt())
}
} }
"delivered" -> { "delivered" -> {
// Untuk status delivered, tampilkan "Beri Ulasan" // Untuk status delivered, tampilkan "Beri Ulasan"
@ -230,13 +234,86 @@ class OrderHistoryAdapter(
} }
} }
} }
"canceled" -> {
statusOrder.apply {
visibility = View.VISIBLE
text = itemView.context.getString(R.string.canceled_orders)
}
}
} }
} }
private fun formatDate(dateString: String): String { private fun formatDate(dateString: String): String {
// In a real app, you would parse the date string and format it return try {
// For this example, just return the string as is val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
return dateString 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)
outputFormat.format(calendar.time)
} ?: dateString
} catch (e: Exception) {
Log.e("DateFormatting", "Error formatting date: ${e.message}")
dateString
}
}
private fun formatDatePay(dateString: String): String {
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())
dueDateFormat.format(calendar.time)
} catch (e: Exception) {
Log.e("DateFormatting", "Error formatting date: ${e.message}")
dateString
}
}
private fun formatShipmentDate(dateString: String, estimate: Int): String {
return try {
// 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)
outputFormat.format(calendar.time)
} ?: dateString
} catch (e: Exception) {
Log.e("ShipmentDateFormatting", "Error formatting shipment date: ${e.message}")
dateString
}
} }
} }
} }

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM18,20L6,20L6,4h7v5h5v11zM8,15.01l1.41,1.41L11,14.84L11,19h2v-4.16l1.59,1.59L16,15.01 12.01,11z"/>
</vector>

View File

@ -0,0 +1,185 @@
<?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.detail.AddEvidencePaymentActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#FFFFFF"
app:navigationIcon="@drawable/ic_back_24"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Kirim Bukti Bayar"
android:textColor="#000000"
android:textSize="18sp"
android:layout_marginVertical="8dp"
android:fontFamily="@font/dmsans_bold" />
</androidx.appcompat.widget.Toolbar>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider">
<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:layout_marginTop="16dp"
android:text="Unggah Foto *"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" />
<androidx.cardview.widget.CardView
android:id="@+id/cardAddPhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#FFFFFF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/tvAddPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tambah Foto"
android:textColor="#1E88E5"
android:textSize="14sp" />
<FrameLayout
android:id="@+id/frameUploadImage"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:padding="8dp">
<ImageView
android:id="@+id/ivUploadedImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="gone" />
<LinearLayout
android:id="@+id/layoutUploadPlaceholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/edit_text_background"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/baseline_upload_file_24" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Metode Pembayaran *"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" />
<Spinner
android:id="@+id/spinnerPaymentMethod"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:minHeight="50dp"
android:padding="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Nomor Rekening / Nomor HP *"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" />
<EditText
android:id="@+id/etAccountNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi nomor rekening atau nomor hp pembayaran"
android:inputType="text"
android:minHeight="50dp"
android:textSize="14sp"
android:padding="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Tanggal Pembayaran *"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" />
<TextView
android:id="@+id/tvPaymentDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:drawableEnd="@drawable/ic_calendar"
android:drawablePadding="8dp"
android:hint="Pilih tanggal"
android:minHeight="50dp"
android:padding="12dp" />
<Button
android:id="@+id/btnSubmit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:background="@drawable/bg_button_filled"
android:text="Kirim"
android:textAllCaps="false"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:padding="12dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -67,7 +67,7 @@
<string name="no_available_orders">Tidak ada order</string> <string name="no_available_orders">Tidak ada order</string>
<string name="item_count_prod">%d produk</string> <string name="item_count_prod">%d produk</string>
<string name="show_more_product">%d produk lainnya</string> <string name="show_more_product">%d produk lainnya</string>
<!-- status order--> <!-- status order-->
<string name="all_orders">Semua Pesanan </string> <string name="all_orders">Semua Pesanan </string>
<string name="pending_orders">Menunggu Tagihan</string> <string name="pending_orders">Menunggu Tagihan</string>
<string name="unpaid_orders">Konfrimasi Bayar</string> <string name="unpaid_orders">Konfrimasi Bayar</string>
@ -94,7 +94,4 @@
<string name="add_review">Beri Ulasan </string> <string name="add_review">Beri Ulasan </string>
</resources> </resources>