fix price and chat date

This commit is contained in:
shaulascr
2025-06-18 16:43:03 +07:00
committed by Gracia Hotmauli
parent 4401daa310
commit 019b469556
21 changed files with 419 additions and 61 deletions

View File

@ -80,7 +80,7 @@ data class Orders(
val cancelReason: String? = null, val cancelReason: String? = null,
@field:SerializedName("total_amount") @field:SerializedName("total_amount")
val totalAmount: String? = null, val totalAmount: Int? = null,
@field:SerializedName("user_id") @field:SerializedName("user_id")
val userId: Int, val userId: Int,

View File

@ -394,7 +394,10 @@ class ChatActivity : AppCompatActivity() {
// Update messages // Update messages
val previousCount = chatAdapter.itemCount val previousCount = chatAdapter.itemCount
chatAdapter.submitList(state.messages) {
val displayItems = viewModel.getDisplayItems()
chatAdapter.submitList(displayItems) {
Log.d(TAG, "Messages submitted to adapter") Log.d(TAG, "Messages submitted to adapter")
// Only auto-scroll for new messages or initial load // Only auto-scroll for new messages or initial load
if (previousCount == 0 || state.messages.size > previousCount) { if (previousCount == 0 || state.messages.size > previousCount) {
@ -426,17 +429,15 @@ class ChatActivity : AppCompatActivity() {
.error(R.drawable.placeholder_image) .error(R.drawable.placeholder_image)
.into(binding.imgProduct) .into(binding.imgProduct)
} }
updateProductCardUI(state.hasProductAttachment)
binding.productContainer.visibility = View.VISIBLE binding.productContainer.visibility = View.GONE
} else { } else {
binding.productContainer.visibility = View.GONE binding.productContainer.visibility = View.GONE
} }
updateInputHint(state) updateInputHint(state)
// Update product card visual feedback
updateProductCardUI(state.hasProductAttachment)
// Update attachment hint // Update attachment hint
if (state.hasAttachment) { if (state.hasAttachment) {
binding.editTextMessage.hint = getString(R.string.image_attached) binding.editTextMessage.hint = getString(R.string.image_attached)

View File

@ -8,6 +8,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.ItemDateHeaderBinding
import com.alya.ecommerce_serang.databinding.ItemMessageProductReceivedBinding import com.alya.ecommerce_serang.databinding.ItemMessageProductReceivedBinding
import com.alya.ecommerce_serang.databinding.ItemMessageProductSentBinding import com.alya.ecommerce_serang.databinding.ItemMessageProductSentBinding
import com.alya.ecommerce_serang.databinding.ItemMessageReceivedBinding import com.alya.ecommerce_serang.databinding.ItemMessageReceivedBinding
@ -17,22 +18,29 @@ import com.bumptech.glide.Glide
class ChatAdapter( class ChatAdapter(
private val onProductClick: ((ProductInfo) -> Unit)? = null private val onProductClick: ((ProductInfo) -> Unit)? = null
) : ListAdapter<ChatUiMessage, RecyclerView.ViewHolder>(ChatMessageDiffCallback()) { ) : ListAdapter<ChatDisplayItem, RecyclerView.ViewHolder>(ChatMessageDiffCallback()) {
companion object { companion object {
private const val VIEW_TYPE_MESSAGE_SENT = 1 private const val VIEW_TYPE_MESSAGE_SENT = 1
private const val VIEW_TYPE_MESSAGE_RECEIVED = 2 private const val VIEW_TYPE_MESSAGE_RECEIVED = 2
private const val VIEW_TYPE_PRODUCT_SENT = 3 private const val VIEW_TYPE_PRODUCT_SENT = 3
private const val VIEW_TYPE_PRODUCT_RECEIVED = 4 private const val VIEW_TYPE_PRODUCT_RECEIVED = 4
private const val VIEW_TYPE_DATE_HEADER = 5
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
val message = getItem(position) val item = getItem(position)
return when { return when (item) {
message.messageType == MessageType.PRODUCT && message.isSentByMe -> VIEW_TYPE_PRODUCT_SENT is ChatDisplayItem.DateHeaderItem -> VIEW_TYPE_DATE_HEADER
message.messageType == MessageType.PRODUCT && !message.isSentByMe -> VIEW_TYPE_PRODUCT_RECEIVED is ChatDisplayItem.MessageItem -> {
message.isSentByMe -> VIEW_TYPE_MESSAGE_SENT val message = item.chatUiMessage
else -> VIEW_TYPE_MESSAGE_RECEIVED when {
message.messageType == MessageType.PRODUCT && message.isSentByMe -> VIEW_TYPE_PRODUCT_SENT
message.messageType == MessageType.PRODUCT && !message.isSentByMe -> VIEW_TYPE_PRODUCT_RECEIVED
message.isSentByMe -> VIEW_TYPE_MESSAGE_SENT
else -> VIEW_TYPE_MESSAGE_RECEIVED
}
}
} }
} }
@ -40,6 +48,10 @@ class ChatAdapter(
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (viewType) { return when (viewType) {
VIEW_TYPE_DATE_HEADER -> {
val binding = ItemDateHeaderBinding.inflate(inflater, parent, false)
DateHeaderViewHolder(binding)
}
VIEW_TYPE_MESSAGE_SENT -> { VIEW_TYPE_MESSAGE_SENT -> {
val binding = ItemMessageSentBinding.inflate(inflater, parent, false) val binding = ItemMessageSentBinding.inflate(inflater, parent, false)
SentMessageViewHolder(binding) SentMessageViewHolder(binding)
@ -61,13 +73,34 @@ class ChatAdapter(
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val message = getItem(position) val item = getItem(position)
when (holder) { when (holder) {
is SentMessageViewHolder -> holder.bind(message) is DateHeaderViewHolder -> {
is ReceivedMessageViewHolder -> holder.bind(message) if (item is ChatDisplayItem.DateHeaderItem) {
is SentProductViewHolder -> holder.bind(message) holder.bind(item)
is ReceivedProductViewHolder -> holder.bind(message) }
}
is SentMessageViewHolder -> {
if (item is ChatDisplayItem.MessageItem) {
holder.bind(item.chatUiMessage)
}
}
is ReceivedMessageViewHolder -> {
if (item is ChatDisplayItem.MessageItem) {
holder.bind(item.chatUiMessage)
}
}
is SentProductViewHolder -> {
if (item is ChatDisplayItem.MessageItem) {
holder.bind(item.chatUiMessage)
}
}
is ReceivedProductViewHolder -> {
if (item is ChatDisplayItem.MessageItem) {
holder.bind(item.chatUiMessage)
}
}
} }
} }
@ -233,17 +266,36 @@ class ChatAdapter(
} }
} }
} }
inner class DateHeaderViewHolder(private val binding: ItemDateHeaderBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: ChatDisplayItem.DateHeaderItem) {
binding.tvDate.text = item.formattedDate
}
}
} }
/** /**
* DiffUtil callback for optimizing RecyclerView updates * DiffUtil callback for optimizing RecyclerView updates
*/ */
class ChatMessageDiffCallback : DiffUtil.ItemCallback<ChatUiMessage>() { class ChatMessageDiffCallback : DiffUtil.ItemCallback<ChatDisplayItem>() {
override fun areItemsTheSame(oldItem: ChatUiMessage, newItem: ChatUiMessage): Boolean { override fun areItemsTheSame(oldItem: ChatDisplayItem, newItem: ChatDisplayItem): Boolean {
return oldItem.id == newItem.id return when {
oldItem is ChatDisplayItem.MessageItem && newItem is ChatDisplayItem.MessageItem ->
oldItem.chatUiMessage.id == newItem.chatUiMessage.id
oldItem is ChatDisplayItem.DateHeaderItem && newItem is ChatDisplayItem.DateHeaderItem ->
oldItem.date == newItem.date
else -> false
}
} }
override fun areContentsTheSame(oldItem: ChatUiMessage, newItem: ChatUiMessage): Boolean { override fun areContentsTheSame(oldItem: ChatDisplayItem, newItem: ChatDisplayItem): Boolean {
return oldItem == newItem return oldItem == newItem
} }
}
sealed class ChatDisplayItem {
data class MessageItem(val chatUiMessage: ChatUiMessage) : ChatDisplayItem()
data class DateHeaderItem(val date: String, val formattedDate: String) : ChatDisplayItem()
} }

View File

@ -16,6 +16,9 @@ import com.alya.ecommerce_serang.utils.SessionManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import javax.inject.Inject import javax.inject.Inject
@ -737,7 +740,8 @@ class ChatViewModel @Inject constructor(
attachment = chatLine.attachment ?: "", attachment = chatLine.attachment ?: "",
status = chatLine.status, status = chatLine.status,
time = formattedTime, time = formattedTime,
isSentByMe = chatLine.senderId == currentUserId isSentByMe = chatLine.senderId == currentUserId,
createdAt = chatLine.createdAt
) )
} }
@ -781,7 +785,8 @@ class ChatViewModel @Inject constructor(
time = formattedTime, time = formattedTime,
isSentByMe = chatItem.senderId == currentUserId, isSentByMe = chatItem.senderId == currentUserId,
messageType = messageType, messageType = messageType,
productInfo = productInfo productInfo = productInfo,
createdAt = chatItem.createdAt
) )
// Fetch product info for non-current-user products // Fetch product info for non-current-user products
@ -923,6 +928,85 @@ class ChatViewModel @Inject constructor(
Log.d(TAG, "ChatViewModel cleared - Disconnecting socket") Log.d(TAG, "ChatViewModel cleared - Disconnecting socket")
socketService.disconnect() socketService.disconnect()
} }
fun getDisplayItems(): List<ChatDisplayItem> {
return transformMessagesToDisplayItems(state.value?.messages ?: emptyList())
}
private fun transformMessagesToDisplayItems(messages: List<ChatUiMessage>): List<ChatDisplayItem> {
if (messages.isEmpty()) return emptyList()
val displayItems = mutableListOf<ChatDisplayItem>()
var lastDate: String? = null
for (message in messages) {
// Extract date from message timestamp
val messageDate = extractDateFromTimestamp(message.createdAt) // You need to implement this
// Add date header if this is a new day
if (messageDate != lastDate) {
val formattedDate = formatDateHeader(messageDate) // You need to implement this
displayItems.add(ChatDisplayItem.DateHeaderItem(messageDate, formattedDate))
lastDate = messageDate
}
// Add the message
displayItems.add(ChatDisplayItem.MessageItem(message))
}
return displayItems
}
private fun extractDateFromTimestamp(timestamp: String): String {
return try {
// Parse ISO 8601 format: "2025-05-27T08:36:53.946Z"
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val date = inputFormat.parse(timestamp)
val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
outputFormat.format(date ?: Date())
} catch (e: Exception) {
Log.e(TAG, "Error parsing timestamp: $timestamp", e)
return timestamp.take(10)
}
}
private fun formatDateHeader(dateString: String): String {
return try {
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val messageDate = dateFormat.parse(dateString) ?: return dateString
val today = Calendar.getInstance()
val yesterday = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -1) }
val messageCalendar = Calendar.getInstance().apply { time = messageDate }
when {
isSameDay(messageCalendar, today) -> "Today"
isSameDay(messageCalendar, yesterday) -> "Yesterday"
isThisYear(messageCalendar, today) -> {
// Show "Mon, Dec 15" format for this year
SimpleDateFormat("EEE, MMM dd", Locale.getDefault()).format(messageDate)
}
else -> {
// Show "Dec 15, 2024" format for other years
SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(messageDate)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error formatting date: $dateString", e)
dateString // Fallback to raw date
}
}
private fun isSameDay(cal1: Calendar, cal2: Calendar): Boolean {
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)
}
private fun isThisYear(messageCalendar: Calendar, today: Calendar): Boolean {
return messageCalendar.get(Calendar.YEAR) == today.get(Calendar.YEAR)
}
} }
enum class MessageType { enum class MessageType {
@ -949,9 +1033,12 @@ data class ChatUiMessage(
val time: String, val time: String,
val isSentByMe: Boolean, val isSentByMe: Boolean,
val messageType: MessageType = MessageType.TEXT, val messageType: MessageType = MessageType.TEXT,
val productInfo: ProductInfo? = null val productInfo: ProductInfo? = null,
val createdAt: String
) )
// representing UI state to screen // representing UI state to screen
data class ChatUiState( data class ChatUiState(
val messages: List<ChatUiMessage> = emptyList(), val messages: List<ChatUiMessage> = emptyList(),

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.home
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -11,6 +12,8 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem
import com.alya.ecommerce_serang.databinding.ItemProductGridBinding import com.alya.ecommerce_serang.databinding.ItemProductGridBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class HorizontalProductAdapter( class HorizontalProductAdapter(
private var products: List<ProductsItem>, private var products: List<ProductsItem>,
@ -32,8 +35,17 @@ class HorizontalProductAdapter(
Log.d("ProductAdapter", "Loading image: $fullImageUrl") Log.d("ProductAdapter", "Loading image: $fullImageUrl")
tvProductName.text = product.name tvProductName.text = product.name
tvProductPrice.text = product.price tvProductPrice.text = formatCurrency(product.price.toDouble())
rating.text = product.rating val ratingStr = product.rating
val ratingValue = ratingStr?.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) {
binding.rating.text = String.format("%.1f", ratingValue)
binding.rating.visibility = View.VISIBLE
} else {
binding.rating.text = "Belum ada rating"
binding.rating.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
// Load image using Glide // Load image using Glide
Glide.with(itemView) Glide.with(itemView)
@ -77,6 +89,11 @@ class HorizontalProductAdapter(
diffResult.dispatchUpdatesTo(this) diffResult.dispatchUpdatesTo(this)
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
class ProductDiffCallback( class ProductDiffCallback(
private val oldList: List<ProductsItem>, private val oldList: List<ProductsItem>,
private val newList: List<ProductsItem> private val newList: List<ProductsItem>

View File

@ -12,6 +12,8 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem
import com.alya.ecommerce_serang.databinding.ItemProductGridBinding import com.alya.ecommerce_serang.databinding.ItemProductGridBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class SearchResultsAdapter( class SearchResultsAdapter(
private val onItemClick: (ProductsItem) -> Unit, private val onItemClick: (ProductsItem) -> Unit,
@ -46,7 +48,7 @@ class SearchResultsAdapter(
fun bind(product: ProductsItem) { fun bind(product: ProductsItem) {
binding.tvProductName.text = product.name binding.tvProductName.text = product.name
binding.tvProductPrice.text = product.price binding.tvProductPrice.text = formatCurrency(product.price.toDouble())
val fullImageUrl = if (product.image.startsWith("/")) { val fullImageUrl = if (product.image.startsWith("/")) {
BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/" BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/"
@ -71,6 +73,11 @@ class SearchResultsAdapter(
super.submitList(list) super.submitList(list)
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
companion object { companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ProductsItem>() { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ProductsItem>() {
override fun areItemsTheSame(oldItem: ProductsItem, newItem: ProductsItem): Boolean { override fun areItemsTheSame(oldItem: ProductsItem, newItem: ProductsItem): Boolean {

View File

@ -48,6 +48,12 @@ class PaymentMethodAdapter(
// Load payment icon if available // Load payment icon if available
if (!payment.qrisImage.isNullOrEmpty()) { if (!payment.qrisImage.isNullOrEmpty()) {
// val fullImageUrl = if (payment.qrisImage.startsWith("/")) {
// BASE_URL + payment.qrisImage.removePrefix("/") // Append base URL if the path starts with "/"
// } else {
// payment.qrisImage// Use as is if it's already a full URL
// }
Glide.with(ivPaymentMethod.context) Glide.with(ivPaymentMethod.context)
.load(payment.qrisImage) .load(payment.qrisImage)
.apply( .apply(

View File

@ -6,6 +6,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostsItem import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ServicesItem import com.alya.ecommerce_serang.data.api.response.customer.order.ServicesItem
import com.alya.ecommerce_serang.databinding.ItemShippingOrderBinding import com.alya.ecommerce_serang.databinding.ItemShippingOrderBinding
import java.text.NumberFormat
import java.util.Locale
class ShippingAdapter( class ShippingAdapter(
private val onItemSelected: (CourierCostsItem, ServicesItem) -> Unit private val onItemSelected: (CourierCostsItem, ServicesItem) -> Unit
@ -30,7 +32,7 @@ class ShippingAdapter(
// Combine courier name and service // Combine courier name and service
courierNameCost.text = "${courierCostsItem.courier} - ${service.service}" courierNameCost.text = "${courierCostsItem.courier} - ${service.service}"
estDate.text = "Estimasi ${service.etd} hari" estDate.text = "Estimasi ${service.etd} hari"
costPrice.text = "Rp${service.cost}" costPrice.text = formatCurrency(service.cost.toDouble())
// Single click handler for both item and radio button // Single click handler for both item and radio button
val onClickAction = { val onClickAction = {
@ -90,6 +92,11 @@ class ShippingAdapter(
} }
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return courierCostsList.sumOf { it.services.size } return courierCostsList.sumOf { it.services.size }
} }

View File

@ -1,8 +1,8 @@
package com.alya.ecommerce_serang.ui.order.detail package com.alya.ecommerce_serang.ui.order.detail
import android.Manifest import android.Manifest
import android.R
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -12,6 +12,7 @@ import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
@ -25,6 +26,7 @@ import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.OrderRepository
@ -37,6 +39,7 @@ import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File import java.io.File
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -59,11 +62,9 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} }
private val paymentMethods = arrayOf( private val paymentMethods = arrayOf(
"Pilih metode pembayaran",
"Transfer Bank", "Transfer Bank",
"E-Wallet", "E-Wallet",
"Virtual Account", "QRIS",
"Cash on Delivery"
) )
// private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> // private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
@ -128,11 +129,8 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} }
private fun setupUI() { private fun setupUI() {
// Set product details\ val paymentMethods = listOf("Transfer Bank", "COD", "QRIS")
val adapter = SpinnerCardAdapter(this, paymentMethods)
// 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 binding.spinnerPaymentMethod.adapter = adapter
} }
@ -219,15 +217,23 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
private fun showImagePickerOptions() { private fun showImagePickerOptions() {
val options = arrayOf( val options = arrayOf(
"Pilih dari Galeri", "Pilih dari Galeri",
"Batal" "Kembali"
) )
androidx.appcompat.app.AlertDialog.Builder(this) val adapter = object : ArrayAdapter<String>(this, R.layout.item_dialog_add_evidence, R.id.tvOption, options) {
.setTitle("Pilih Bukti Pembayaran") override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
.setItems(options) { dialog, which -> val view = super.getView(position, convertView, parent)
val divider = view.findViewById<View>(R.id.divider)
divider.visibility = if (position == count - 1) View.GONE else View.VISIBLE
return view
}
}
AlertDialog.Builder(this)
.setAdapter(adapter) { dialog, which ->
when (which) { when (which) {
0 -> openGallery() // Gallery 0 -> openGallery()
1 -> dialog.dismiss() // Cancel 1 -> dialog.dismiss()
} }
} }
.show() .show()
@ -440,6 +446,8 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} }
companion object { companion object {
private const val PERMISSION_REQUEST_CODE = 100 private const val PERMISSION_REQUEST_CODE = 100
private const val TAG = "AddEvidenceActivity" private const val TAG = "AddEvidenceActivity"

View File

@ -17,6 +17,7 @@ import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.databinding.ActivityPaymentBinding import com.alya.ecommerce_serang.databinding.ActivityPaymentBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -100,6 +101,7 @@ class PaymentActivity : AppCompatActivity() {
// Log.d(TAG, "Button clicked - showing toast") // Log.d(TAG, "Button clicked - showing toast")
// Toast.makeText(this@PaymentActivity, "Button works! OrderID: $orderId", Toast.LENGTH_LONG).show() // Toast.makeText(this@PaymentActivity, "Button works! OrderID: $orderId", Toast.LENGTH_LONG).show()
// } // }
binding.btnUploadPaymentProof.apply { binding.btnUploadPaymentProof.apply {
isEnabled = true isEnabled = true
isClickable = true isClickable = true
@ -134,7 +136,7 @@ class PaymentActivity : AppCompatActivity() {
Log.d(TAG, "Order details received: $order") Log.d(TAG, "Order details received: $order")
// Set total amount // Set total amount
binding.tvTotalAmount.text = order.totalAmount ?: "Rp0" binding.tvTotalAmount.text = formatCurrency(order.totalAmount?.toDouble() ?: 0.00)
Log.d(TAG, "Total Amount: ${order.totalAmount}") Log.d(TAG, "Total Amount: ${order.totalAmount}")
@ -202,6 +204,11 @@ class PaymentActivity : AppCompatActivity() {
} }
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
private fun showInstructions(type: String) { private fun showInstructions(type: String) {
// Implementasi tampilkan instruksi // Implementasi tampilkan instruksi
val instructions = when (type) { val instructions = when (type) {

View File

@ -0,0 +1,33 @@
package com.alya.ecommerce_serang.ui.order.detail
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.alya.ecommerce_serang.R
class SpinnerCardAdapter(
context: Context,
private val items: List<String>
) : ArrayAdapter<String>(context, 0, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
// View shown when Spinner is collapsed
return createCardView(position, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
// View shown for dropdown items
return createCardView(position, convertView, parent)
}
private fun createCardView(position: Int, convertView: View?, parent: ViewGroup): View {
val inflater = LayoutInflater.from(context)
val view = convertView ?: inflater.inflate(R.layout.item_dialog_spinner_card, parent, false)
val textView = view.findViewById<TextView>(R.id.tvOption)
textView.text = items[position]
return view
}
}

View File

@ -6,6 +6,7 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListItemsItem import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListItemsItem
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -39,11 +40,18 @@ class DetailOrderItemsAdapter : RecyclerView.Adapter<DetailOrderItemsAdapter.Det
private val tvProductName: TextView = itemView.findViewById(R.id.tvProductName) private val tvProductName: TextView = itemView.findViewById(R.id.tvProductName)
private val tvQuantity: TextView = itemView.findViewById(R.id.tvQuantity) private val tvQuantity: TextView = itemView.findViewById(R.id.tvQuantity)
private val tvPrice: TextView = itemView.findViewById(R.id.tvPrice) private val tvPrice: TextView = itemView.findViewById(R.id.tvPrice)
private val tvStoreName: TextView = itemView.findViewById(R.id.tvStoreName)
fun bind(item: OrderListItemsItem) { fun bind(item: OrderListItemsItem) {
val fullImageUrl = if (item.productImage.startsWith("/")) {
BASE_URL + item.productImage.removePrefix("/") // Append base URL if the path starts with "/"
} else {
item.productImage // Use as is if it's already a full URL
}
// Load product image // Load product image
Glide.with(itemView.context) Glide.with(itemView.context)
.load(item.productImage) .load(fullImageUrl)
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image) .error(R.drawable.placeholder_image)
.into(ivProduct) .into(ivProduct)
@ -52,7 +60,8 @@ class DetailOrderItemsAdapter : RecyclerView.Adapter<DetailOrderItemsAdapter.Det
tvProductName.text = item.productName tvProductName.text = item.productName
tvQuantity.text = "${item.quantity} buah" tvQuantity.text = "${item.quantity} buah"
tvPrice.text = "Rp${newPrice}" tvPrice.text = newPrice
tvStoreName.text = item.storeName
} }
} }

View File

@ -42,6 +42,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson import com.google.gson.Gson
import java.io.File import java.io.File
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -172,7 +173,6 @@ class DetailOrderStatusActivity : AppCompatActivity() {
try { try {
// Set order date and payment deadline // Set order date and payment deadline
binding.tvOrderDate.text = formatDate(orders.createdAt) 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}") Log.d(TAG, "populateOrderDetails: Order created at ${orders.createdAt}, formatted as ${binding.tvOrderDate.text}")
@ -197,10 +197,10 @@ class DetailOrderStatusActivity : AppCompatActivity() {
Log.d(TAG, "populateOrderDetails: Payment method=${orders.payInfoName ?: "Tidak tersedia"}") Log.d(TAG, "populateOrderDetails: Payment method=${orders.payInfoName ?: "Tidak tersedia"}")
// Set subtotal, shipping cost, and total // Set subtotal, shipping cost, and total
val subtotal = orders.totalAmount?.toIntOrNull()?.minus(orders.shipmentPrice.toIntOrNull() ?: 0) ?: 0 val subtotal = orders.totalAmount?.minus(orders.shipmentPrice.toIntOrNull() ?: 0) ?: 0
binding.tvSubtotal.text = "Rp$subtotal" binding.tvSubtotal.text = formatCurrency(subtotal.toDouble())
binding.tvShippingCost.text = "Rp${orders.shipmentPrice}" binding.tvShippingCost.text = formatCurrency(orders.shipmentPrice.toDouble())
binding.tvTotal.text = "Rp${orders.totalAmount}" binding.tvTotal.text = formatCurrency(orders.totalAmount?.toDouble() ?: 0.00)
Log.d(TAG, "populateOrderDetails: Subtotal=$subtotal, Shipping=${orders.shipmentPrice}, Total=${orders.totalAmount}") Log.d(TAG, "populateOrderDetails: Subtotal=$subtotal, Shipping=${orders.shipmentPrice}, Total=${orders.totalAmount}")
@ -255,6 +255,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Belum Dibayar" binding.tvStatusHeader.text = "Belum Dibayar"
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan ini harus dibayar sebelum ${formatDatePay(orders.updatedAt)}" binding.tvStatusNote.text = "Pesanan ini harus dibayar sebelum ${formatDatePay(orders.updatedAt)}"
binding.tvPaymentDeadlineLabel.text = "Batas Bayar:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt)
// Set buttons // Set buttons
binding.btnSecondary.apply { binding.btnSecondary.apply {
@ -286,6 +288,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Sudah Dibayar" binding.tvStatusHeader.text = "Sudah Dibayar"
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}" binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}"
binding.tvPaymentDeadlineLabel.text = "Batas konfirmasi penjual:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt)
// Set buttons // Set buttons
binding.btnSecondary.apply { binding.btnSecondary.apply {
@ -304,6 +308,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Sedang Diproses" binding.tvStatusHeader.text = "Sedang Diproses"
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Penjual sedang memproses pesanan Anda" binding.tvStatusNote.text = "Penjual sedang memproses pesanan Anda"
binding.tvPaymentDeadlineLabel.text = "Batas diproses penjual:"
binding.tvPaymentDeadline.text = formatDatePay(orders.updatedAt)
binding.btnSecondary.apply { binding.btnSecondary.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -326,6 +332,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Sudah Dikirim" binding.tvStatusHeader.text = "Sudah Dikirim"
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan Anda sedang dalam perjalanan. Akan sampai sekitar ${formatShipmentDate(orders.updatedAt, orders.etd ?: "0")}" binding.tvStatusNote.text = "Pesanan Anda sedang dalam perjalanan. Akan sampai sekitar ${formatShipmentDate(orders.updatedAt, orders.etd ?: "0")}"
binding.tvPaymentDeadlineLabel.text = "Estimasi pesanan sampai:"
binding.tvPaymentDeadline.text = formatShipmentDate(orders.updatedAt, orders.etd ?: "0")
binding.btnSecondary.apply { binding.btnSecondary.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -358,6 +366,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Pesanan Selesai" binding.tvStatusHeader.text = "Pesanan Selesai"
binding.tvStatusNote.visibility = View.GONE binding.tvStatusNote.visibility = View.GONE
binding.tvPaymentDeadlineLabel.text = "Pesanan selesai:"
binding.tvPaymentDeadline.text = formatDate(orders.autoCompletedAt.toString())
binding.btnPrimary.apply { binding.btnPrimary.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -379,6 +389,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
binding.tvStatusHeader.text = "Pesanan Selesai" binding.tvStatusHeader.text = "Pesanan Selesai"
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Pesanan dibatalkan: ${orders.cancelReason ?: "Alasan tidak diberikan"}" binding.tvStatusNote.text = "Pesanan dibatalkan: ${orders.cancelReason ?: "Alasan tidak diberikan"}"
binding.tvPaymentDeadlineLabel.text = "Tanggal dibatalkan: "
binding.tvPaymentDeadline.text = formatDate(orders.cancelDate.toString())
binding.btnSecondary.apply { binding.btnSecondary.apply {
visibility = View.GONE visibility = View.GONE
@ -604,7 +616,8 @@ class DetailOrderStatusActivity : AppCompatActivity() {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC") inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("HH:mm dd MMMM yyyy", Locale("id", "ID")) val timeFormat = SimpleDateFormat("HH:mm", Locale("id", "ID"))
val dateFormat = SimpleDateFormat("dd MMMM yyyy", Locale("id", "ID"))
val date = inputFormat.parse(dateString) val date = inputFormat.parse(dateString)
@ -614,7 +627,10 @@ class DetailOrderStatusActivity : AppCompatActivity() {
calendar.set(Calendar.HOUR_OF_DAY, 23) calendar.set(Calendar.HOUR_OF_DAY, 23)
calendar.set(Calendar.MINUTE, 59) calendar.set(Calendar.MINUTE, 59)
val formatted = outputFormat.format(calendar.time) val timePart = timeFormat.format(calendar.time)
val datePart = dateFormat.format(calendar.time)
val formatted = "$timePart\n$datePart"
Log.d(TAG, "formatDate: Formatted date: $formatted") Log.d(TAG, "formatDate: Formatted date: $formatted")
formatted formatted
} ?: dateString } ?: dateString
@ -640,9 +656,13 @@ class DetailOrderStatusActivity : AppCompatActivity() {
calendar.add(Calendar.HOUR, 24) calendar.add(Calendar.HOUR, 24)
val dueDate = calendar.time val dueDate = calendar.time
// Format due date for display val timeFormat = SimpleDateFormat("HH:mm", Locale("id", "ID"))
val dueDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()) val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale("id", "ID"))
val formatted = dueDateFormat.format(calendar.time)
val timePart = timeFormat.format(dueDate)
val datePart = dateFormat.format(dueDate)
val formatted = "$timePart\n$datePart"
Log.d(TAG, "formatDatePay: Formatted payment date: $formatted") Log.d(TAG, "formatDatePay: Formatted payment date: $formatted")
formatted formatted
@ -729,6 +749,11 @@ class DetailOrderStatusActivity : AppCompatActivity() {
} }
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "onDestroy: Cleaning up references") Log.d(TAG, "onDestroy: Cleaning up references")
super.onDestroy() super.onDestroy()

View File

@ -11,6 +11,8 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class OtherProductAdapter ( class OtherProductAdapter (
private var products: List<ProductsItem>, private var products: List<ProductsItem>,
@ -32,7 +34,7 @@ class OtherProductAdapter (
Log.d("ProductAdapter", "Loading image: $fullImageUrl") Log.d("ProductAdapter", "Loading image: $fullImageUrl")
tvProductName.text = product.name tvProductName.text = product.name
tvProductPrice.text = product.price tvProductPrice.text = formatCurrency(product.price.toDouble())
rating.text = product.rating rating.text = product.rating
// Load image using Glide // Load image using Glide
@ -77,6 +79,11 @@ class OtherProductAdapter (
diffResult.dispatchUpdatesTo(this) diffResult.dispatchUpdatesTo(this)
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
class ProductDiffCallback( class ProductDiffCallback(
private val oldList: List<ProductsItem>, private val oldList: List<ProductsItem>,
private val newList: List<ProductsItem> private val newList: List<ProductsItem>

View File

@ -11,6 +11,8 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem
import com.alya.ecommerce_serang.databinding.ItemProductGridBinding import com.alya.ecommerce_serang.databinding.ItemProductGridBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class ListProductAdapter( class ListProductAdapter(
private var products: List<ProductsItem>, private var products: List<ProductsItem>,
@ -24,7 +26,7 @@ class ListProductAdapter(
fun bind(product: ProductsItem) = with(binding) { fun bind(product: ProductsItem) = with(binding) {
tvProductName.text = product.name tvProductName.text = product.name
tvProductPrice.text = product.price tvProductPrice.text = formatCurrency(product.price.toDouble())
rating.text = product.rating rating.text = product.rating
val fullImageUrl = if (product.image.startsWith("/")) { val fullImageUrl = if (product.image.startsWith("/")) {
@ -68,6 +70,11 @@ class ListProductAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
class ProductDiffCallback( class ProductDiffCallback(
private val oldList: List<ProductsItem>, private val oldList: List<ProductsItem>,
private val newList: List<ProductsItem> private val newList: List<ProductsItem>

View File

@ -378,7 +378,9 @@ class ChatStoreActivity : AppCompatActivity() {
// Update messages // Update messages
val previousCount = chatAdapter.itemCount val previousCount = chatAdapter.itemCount
chatAdapter.submitList(state.messages) { val displayItems = viewModel.getDisplayItems()
chatAdapter.submitList(displayItems) {
Log.d(TAG, "Messages submitted to adapter") Log.d(TAG, "Messages submitted to adapter")
// Only auto-scroll for new messages or initial load // Only auto-scroll for new messages or initial load
if (previousCount == 0 || state.messages.size > previousCount) { if (previousCount == 0 || state.messages.size > previousCount) {

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/black_200" />
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingVertical="8dp">
<TextView
android:id="@+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_date_header"
android:paddingHorizontal="12dp"
android:paddingVertical="4dp"
android:text="Today"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="6dp"
android:foreground="?android:attr/selectableItemBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/tvOption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_semibold"
android:text="Item 1"
android:textColor="@color/black_500"
android:textSize="16sp" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:layout_marginTop="4dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
card_view:cardElevation="4dp"
card_view:cardCornerRadius="8dp"
android:foreground="?android:attr/selectableItemBackground">
<TextView
android:id="@+id/tvOption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="@color/black_500"
android:text="Item 1"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp"
android:gravity="center_vertical" />
</androidx.cardview.widget.CardView>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp"> android:paddingHorizontal="8dp">
<TextView <TextView
android:id="@+id/tvStoreName" android:id="@+id/tvStoreName"
@ -68,6 +68,7 @@
android:textColor="@color/blue_500" android:textColor="@color/blue_500"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold" android:textStyle="bold"
android:paddingBottom="4dp"
app:layout_constraintStart_toEndOf="@+id/ivProduct" app:layout_constraintStart_toEndOf="@+id/ivProduct"
app:layout_constraintTop_toBottomOf="@+id/tvQuantity" /> app:layout_constraintTop_toBottomOf="@+id/tvQuantity" />