Compare commits

10 Commits

66 changed files with 1234 additions and 298 deletions

View File

@ -4,14 +4,27 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-08-23T01:49:41.982701700Z">
<DropdownSelection timestamp="2025-08-29T16:47:53.924316Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_9a.avd" />
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_9_3.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
<DialogSelection>
<targets>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_9_3.avd" />
</handle>
</Target>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_8.avd" />
</handle>
</Target>
</targets>
</DialogSelection>
</SelectionState>
</selectionStates>
</component>

View File

@ -0,0 +1,24 @@
package com.alya.ecommerce_serang.data.api.response.auth
import com.google.gson.annotations.SerializedName
data class DeleteFCMResponse(
@field:SerializedName("message")
val message: String,
@field:SerializedName("user")
val user: UserFCM
)
data class UserFCM(
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("email")
val email: String
)

View File

@ -62,6 +62,9 @@ data class Product(
@field:SerializedName("wholesale_min_item")
val wholesaleMinItem: Int? = null,
@field:SerializedName("status")
val status: String,
@field:SerializedName("min_order")
val minOrder: Int,

View File

@ -27,6 +27,7 @@ import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
import com.alya.ecommerce_serang.data.api.response.auth.DeleteFCMResponse
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.ListNotifResponse
@ -106,6 +107,10 @@ interface ApiService {
@Body verifRegisReq: VerifRegisReq
):VerifRegisterResponse
@PUT("deletefcm")
suspend fun deleteFCMToken (
): DeleteFCMResponse
@Multipart
@POST("registerstore")
suspend fun registerStore(

View File

@ -12,6 +12,7 @@ import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
import com.alya.ecommerce_serang.data.api.response.auth.DeleteFCMResponse
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
@ -541,6 +542,10 @@ class UserRepository(private val apiService: ApiService) {
}
}
suspend fun deleteFCMToken(): DeleteFCMResponse{
return apiService.deleteFCMToken()
}
companion object{
private const val TAG = "UserRepository"
}

View File

@ -5,10 +5,9 @@ import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.Result
@ -16,6 +15,7 @@ import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityLoginBinding
import com.alya.ecommerce_serang.ui.MainActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.LoginViewModel
import com.google.firebase.FirebaseApp
@ -39,9 +39,9 @@ class LoginActivity : AppCompatActivity() {
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge()
//
// WindowCompat.setDecorFitsSystemWindows(window, false)
// enableEdgeToEdge()
setupListeners()
observeLoginState()
@ -83,6 +83,11 @@ class LoginActivity : AppCompatActivity() {
retrieveFCMToken()
// sessionManager.saveUserId(response.userId)
PopUpDialog.showConfirmDialog(
context = this,
iconRes = R.drawable.checkmark__1_,
title = "Berhasil Masuk"
)
Toast.makeText(this, "Berhasil masuk", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
@ -90,6 +95,11 @@ class LoginActivity : AppCompatActivity() {
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e("LoginActivity", "Login Failed: ${result.exception.message}")
PopUpDialog.showConfirmDialog(
context = this,
iconRes = R.drawable.ic_cancel,
title = "Gagal Masuk"
)
Toast.makeText(this, "Gagal masuk", Toast.LENGTH_LONG).show()
}
is Result.Loading -> {

View File

@ -5,13 +5,14 @@ import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityResetPassBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.viewmodel.LoginViewModel
class ResetPassActivity : AppCompatActivity() {
@ -36,7 +37,7 @@ class ResetPassActivity : AppCompatActivity() {
setupToolbar()
setupUI()
observeResetPassword()
}
private fun setupToolbar(){
@ -98,26 +99,23 @@ class ResetPassActivity : AppCompatActivity() {
private fun handleSuccess(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
// Show success dialog and navigate back to login
AlertDialog.Builder(this)
.setTitle("Berhasil Ubah Password")
.setMessage(message)
.setPositiveButton("OK") { _, _ ->
// Navigate back to login activity
finish()
}
.setCancelable(false)
.show()
PopUpDialog.showConfirmDialog(
context = this,
iconRes = R.drawable.checkmark__1_,
title = "Berhasil Ubah Password",
positiveText = "OK"
)
}
private fun handleError(errorMessage: String) {
Log.e(TAG, "Error: $errorMessage")
// Optionally show error dialog
AlertDialog.Builder(this)
.setTitle("Gagal Ubah Password")
.setMessage(errorMessage)
.setPositiveButton("OK", null)
.show()
PopUpDialog.showConfirmDialog(
context = this,
iconRes = R.drawable.ic_cancel,
title = "Gagal Ubah Password",
message = errorMessage,
positiveText = "OK"
)
}
}

View File

@ -27,6 +27,7 @@ import com.alya.ecommerce_serang.ui.order.address.SubdsitrictAdapter
import com.alya.ecommerce_serang.ui.order.address.ViewState
import com.alya.ecommerce_serang.ui.order.address.VillagesAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
import com.google.android.material.progressindicator.LinearProgressIndicator
@ -108,7 +109,16 @@ class RegisterStep3Fragment : Fragment() {
}
binding.btnRegister.setOnClickListener {
submitAddress()
PopUpDialog.showConfirmDialog(
context = requireContext(),
title = "Apakah anda yakin data anda sudah benar?",
message = "Pastikan data yang dimasukkan sudah benar",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
submitAddress()
}
)
}
// Observe address submission state
@ -498,6 +508,7 @@ class RegisterStep3Fragment : Fragment() {
private fun showRegistrationSuccess() {
// Now we can show the success message for the overall registration process
Toast.makeText(requireContext(), "Berhasil mendaftarkan akun", Toast.LENGTH_LONG).show()
sessionManager.clearAll()

View File

@ -43,8 +43,6 @@ class CartActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
binding = ActivityCartBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -201,7 +199,8 @@ class CartActivity : AppCompatActivity() {
viewModel.errorMessage.observe(this) { errorMessage ->
errorMessage?.let {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
binding.emptyCart.visibility = View.VISIBLE
Log.e("CartActivity", "Error message: $it")
}
}
@ -254,6 +253,10 @@ class CartActivity : AppCompatActivity() {
storeAdapter.updateWholesaleStatus(wholesaleStatusMap, wholesalePriceMap)
}
}
viewModel.productImages.observe(this) { productImages ->
storeAdapter.updateProductImages(productImages)
}
}
private fun showEmptyState(isEmpty: Boolean) {
@ -272,5 +275,5 @@ class CartActivity : AppCompatActivity() {
val format = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
return format.format(amount).replace("Rp", "Rp ")
}
}
}

View File

@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
import com.alya.ecommerce_serang.data.api.response.customer.product.CartItemCheckoutInfo
import com.alya.ecommerce_serang.data.api.response.customer.product.Product
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.launch
@ -52,6 +53,12 @@ class CartViewModel(private val repository: OrderRepository) : ViewModel() {
private val _hasConsistentWholesaleStatus = MutableLiveData<Boolean>(true)
val hasConsistentWholesaleStatus: LiveData<Boolean> = _hasConsistentWholesaleStatus
private val _productDetail = MutableLiveData<Product?>()
val productDetail: LiveData<Product?> get() = _productDetail
private val _productImages = MutableLiveData<Map<Int, String>>()
val productImages: LiveData<Map<Int, String>> = _productImages
fun getCart() {
_isLoading.value = true
_errorMessage.value = null
@ -62,6 +69,12 @@ class CartViewModel(private val repository: OrderRepository) : ViewModel() {
_cartItems.value = result.data
_isLoading.value = false
result.data.forEach { store ->
store.cartItems.forEach { item ->
loadProductImage(item.productId)
}
}
// After loading cart items, check wholesale status
checkWholesaleStatus()
}
@ -404,4 +417,29 @@ class CartViewModel(private val repository: OrderRepository) : ViewModel() {
_hasConsistentWholesaleStatus.value = allSameStatus
}
fun loadProductImage(productId: Int) {
viewModelScope.launch {
try {
val result = repository.fetchProductDetail(productId)
val imageUrl = result?.product?.image ?: ""
val currentMap = _productImages.value?.toMutableMap() ?: mutableMapOf()
currentMap[productId] = imageUrl
_productImages.value = currentMap
} catch (e: Exception) {
Log.e("CartViewModel", "Error loading product image: ${e.message}")
}
}
}
// fun loadProductDetail(productId: Int) {
// viewModelScope.launch {
// val result = repository.fetchProductDetail(productId)
// val currentMap = _productImages.value?.toMutableMap() ?: mutableMapOf()
// currentMap[productId] = result?.product?.image ?: ""
// _productImages.value = currentMap
// }
// }
}

View File

@ -11,6 +11,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
@ -30,6 +31,12 @@ class StoreAdapter(
private var activeStoreId: Int? = null
private var wholesaleStatusMap: Map<Int, Boolean> = mapOf()
private var wholesalePriceMap: Map<Int, Double> = mapOf()
private var productImages: Map<Int, String> = emptyMap()
fun updateProductImages(newImages: Map<Int, String>) {
productImages = newImages
notifyDataSetChanged()
}
companion object {
private const val VIEW_TYPE_STORE = 0
@ -135,7 +142,8 @@ class StoreAdapter(
wholesalePrice,
{ isChecked -> onItemCheckChanged(cartItem.cartItemId, store.storeId, isChecked) },
{ quantity -> onItemQuantityChanged(cartItem.cartItemId, quantity) },
{ onItemDeleted(cartItem.cartItemId) }
{ onItemDeleted(cartItem.cartItemId) },
productImages
)
}
}
@ -197,7 +205,8 @@ class StoreAdapter(
wholesalePrice: Double?,
onCheckedChange: (Boolean) -> Unit,
onQuantityChanged: (Int) -> Unit,
onDelete: () -> Unit
onDelete: () -> Unit,
productImages: Map<Int, String>
) {
// Set product name
tvProductName.text = cartItem.productName
@ -216,20 +225,6 @@ class StoreAdapter(
// Set quantity
tvQuantity.text = cartItem.quantity.toString()
// Visual indication for wholesale items
// if (isWholesale) {
// // You can add a background or border to indicate wholesale items
// // For example:
//// itemView.setBackgroundResource(R.drawable.bg_wholesale_item)
// // If you don't have this drawable, you can use a simple color tint instead:
// itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.wholesale_item_bg))
// } else {
// // Reset to default background
//// itemView.setBackgroundResource(R.drawable.bg_normal_item)
// // Or if you don't have this drawable:
// itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.normal_item_bg))
// }
// Set checkbox state without triggering listener
cbItem.setOnCheckedChangeListener(null)
cbItem.isChecked = isSelected
@ -247,11 +242,16 @@ class StoreAdapter(
onCheckedChange(isChecked)
}
// Load product image
val fullImageUrl = when (val img = productImages[cartItem.productId]) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> null
}
Glide.with(itemView.context)
.load("https://example.com/images/${cartItem.productId}.jpg") // Assume image URL based on product ID
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(ivProduct)
// Quantity control

View File

@ -1,9 +1,11 @@
package com.alya.ecommerce_serang.ui.order
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
@ -13,37 +15,57 @@ import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class CartCheckoutAdapter(private val checkoutData: CheckoutData) :
RecyclerView.Adapter<CartCheckoutAdapter.SellerViewHolder>() {
class CartCheckoutAdapter(
private val checkoutData: CheckoutData
) : RecyclerView.Adapter<CartCheckoutAdapter.SellerViewHolder>() {
class SellerViewHolder(val binding: ItemOrderSellerBinding) : RecyclerView.ViewHolder(binding.root)
private var productImages: Map<Int, String> = emptyMap()
private val viewHolders = mutableListOf<SellerViewHolder>() // Keep references
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
val binding = ItemOrderSellerBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return SellerViewHolder(binding)
}
override fun getItemCount(): Int = 1 // Only one seller
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
with(holder.binding) {
// Set seller name
tvStoreName.text = checkoutData.sellerName
// Set up products RecyclerView with multiple items
rvSellerOrderProduct.apply {
layoutManager = LinearLayoutManager(context)
adapter = MultiCartItemsAdapter(checkoutData.cartItems)
class SellerViewHolder(val binding: ItemOrderSellerBinding) : RecyclerView.ViewHolder(binding.root) {
val childAdapter = MultiCartItemsAdapter(emptyList(), emptyMap())
init {
binding.rvSellerOrderProduct.apply {
layoutManager = LinearLayoutManager(binding.root.context)
adapter = childAdapter
isNestedScrollingEnabled = false
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
val binding = ItemOrderSellerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val holder = SellerViewHolder(binding)
viewHolders.add(holder) // Keep reference
return holder
}
fun updateProductImages(newImages: Map<Int, String>) {
productImages = newImages
// Update all existing child adapters
viewHolders.forEach { holder ->
holder.childAdapter.updateProductImages(newImages)
}
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
holder.binding.tvStoreName.text = checkoutData.sellerName
holder.childAdapter.updateData(checkoutData.cartItems)
holder.childAdapter.updateProductImages(productImages) // Apply current images
}
override fun onViewRecycled(holder: SellerViewHolder) {
super.onViewRecycled(holder)
viewHolders.remove(holder) // Clean up reference
}
}
class MultiCartItemsAdapter(private val cartItems: List<CartItemsItem>) :
RecyclerView.Adapter<MultiCartItemsAdapter.CartItemViewHolder>() {
class MultiCartItemsAdapter(
private var cartItems: List<CartItemsItem> = emptyList(),
private var productImages: Map<Int, String> = emptyMap()
) : RecyclerView.Adapter<MultiCartItemsAdapter.CartItemViewHolder>() {
class CartItemViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
@ -56,22 +78,55 @@ class MultiCartItemsAdapter(private val cartItems: List<CartItemsItem>) :
override fun getItemCount(): Int = cartItems.size
fun updateProductImages(images: Map<Int, String>) {
Log.d("MultiCartItemsAdapter", "updateProductImages called with: $images")
Log.d("MultiCartItemsAdapter", "Current cartItems productIds: ${cartItems.map { it.productId }}")
productImages = images
notifyDataSetChanged()
Log.d("MultiCartItemsAdapter", "notifyDataSetChanged() called")
}
override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
val item = cartItems[position]
Log.d("MultiCartItemsAdapter", "onBindViewHolder - position: $position, productId: ${item.productId}")
Log.d("MultiCartItemsAdapter", "Available images: $productImages")
with(holder.binding) {
// Set cart item details
tvProductName.text = item.productName
tvProductQuantity.text = "${item.quantity} buah"
tvProductPrice.text = formatCurrency(item.price.toDouble())
// Load placeholder image
val img = productImages[item.productId]
Log.d("MultiCartItemsAdapter", "Image for productId ${item.productId}: $img")
val fullImageUrl = when (img) {
is String -> {
val url = if (img.startsWith("/")) BASE_URL + img.substring(1) else img
Log.d("MultiCartItemsAdapter", "Full image URL: $url")
url
}
else -> {
Log.d("MultiCartItemsAdapter", "No image found, using placeholder")
null
}
}
Log.d("MultiCartItemsAdapter", "Loading image with Glide: $fullImageUrl")
Glide.with(ivProduct.context)
.load(R.drawable.placeholder_image)
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image) // Add error handling
.into(ivProduct)
}
}
// Minimal helpers to update adapter data from parent adapter
fun updateData(items: List<CartItemsItem>) {
cartItems = items
notifyDataSetChanged()
}
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")

View File

@ -36,6 +36,8 @@ class CheckoutActivity : AppCompatActivity() {
private lateinit var binding: ActivityCheckoutBinding
private lateinit var sessionManager: SessionManager
private var paymentAdapter: PaymentMethodAdapter? = null
private var cartCheckoutAdapter: CartCheckoutAdapter? = null
private var checkoutSellerAdapter: CheckoutSellerAdapter? = null
private var paymentMethodsLoaded = false
private val viewModel: CheckoutViewModel by viewModels {
@ -210,6 +212,13 @@ class CheckoutActivity : AppCompatActivity() {
finish()
}
}
viewModel.productImages.observe(this) { images ->
Log.d("CheckoutActivity", "Product images updated: ${images.keys}")
// Update adapter when images arrive
cartCheckoutAdapter?.updateProductImages(images)
checkoutSellerAdapter?.updateProductImages(images)
}
}
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<DetailPaymentItem>) {
@ -271,24 +280,44 @@ class CheckoutActivity : AppCompatActivity() {
}
private fun setupProductRecyclerView(checkoutData: CheckoutData) {
val adapter = if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
CheckoutSellerAdapter(checkoutData)
if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
Log.d("CheckoutActivity", "Using CheckoutSellerAdapter")
val adapter = CheckoutSellerAdapter(checkoutData)
// Keep reference for image updates - create a field in your activity
checkoutSellerAdapter = adapter
binding.rvProductItems.apply {
layoutManager = LinearLayoutManager(this@CheckoutActivity)
this.adapter = adapter
isNestedScrollingEnabled = false
}
// Load images for cart items
if (!checkoutData.isBuyNow) {
checkoutData.cartItems.forEach { item ->
viewModel.loadProductImage(item.productId)
}
}
} else {
CartCheckoutAdapter(checkoutData)
}
Log.d("CheckoutActivity", "Using CartCheckoutAdapter")
Log.d("CheckoutActivity", "Cart items count: ${checkoutData.cartItems.size}")
binding.rvProductItems.apply {
layoutManager = LinearLayoutManager(this@CheckoutActivity)
this.adapter = adapter
isNestedScrollingEnabled = false
}
// Create adapter and keep reference
cartCheckoutAdapter = CartCheckoutAdapter(checkoutData)
// if (checkoutData.cartItems.isEmpty()) {
// // Show empty products state
// binding.containerEmptyProducts.visibility = View.VISIBLE
// binding.rvProductItems.visibility = View.GONE
// return
// }
binding.rvProductItems.apply {
layoutManager = LinearLayoutManager(this@CheckoutActivity)
adapter = cartCheckoutAdapter
isNestedScrollingEnabled = false
}
// Load images for each product
checkoutData.cartItems.forEach { item ->
Log.d("CheckoutActivity", "Loading image for productId: ${item.productId}")
viewModel.loadProductImage(item.productId)
}
}
binding.containerEmptyProducts.visibility = View.GONE
binding.rvProductItems.visibility = View.VISIBLE
@ -375,7 +404,7 @@ class CheckoutActivity : AppCompatActivity() {
if (validateOrder()) {
PopUpDialog.showConfirmDialog(
context = this,
title = "Apakah anda yakin inging membuat pesanan?",
title = "Apakah anda yakin membuat pesanan?",
message = "Pastikan data yang dimasukkan sudah benar",
positiveText = "Ya",
negativeText = "Tidak",

View File

@ -11,31 +11,53 @@ import com.alya.ecommerce_serang.databinding.ItemOrderSellerBinding
class CheckoutSellerAdapter(private val checkoutData: CheckoutData) :
RecyclerView.Adapter<CheckoutSellerAdapter.SellerViewHolder>() {
private var productImages: Map<Int, String> = emptyMap()
private var currentViewHolder: SellerViewHolder? = null
class SellerViewHolder(val binding: ItemOrderSellerBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
val binding = ItemOrderSellerBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return SellerViewHolder(binding)
val holder = SellerViewHolder(binding)
currentViewHolder = holder
return holder
}
override fun getItemCount(): Int = 1 // Only one seller
fun updateProductImages(newImages: Map<Int, String>) {
productImages = newImages
currentViewHolder?.let { holder ->
// Update the nested adapter
val adapter = holder.binding.rvSellerOrderProduct.adapter
when (adapter) {
is SingleCartItemAdapter -> adapter.updateProductImages(newImages)
is SingleProductAdapter -> {
// For SingleProductAdapter, you might need to update differently
// since it uses checkoutData.productImageUrl
}
}
}
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
currentViewHolder = holder
with(holder.binding) {
// Set seller name
tvStoreName.text = checkoutData.sellerName
// Set up products RecyclerView
rvSellerOrderProduct.apply {
layoutManager = LinearLayoutManager(context)
adapter = if (checkoutData.isBuyNow) {
// Single product for Buy Now
SingleProductAdapter(checkoutData)
} else {
// Single cart item
SingleCartItemAdapter(checkoutData.cartItems.first())
SingleCartItemAdapter(checkoutData.cartItems.first()).also { adapter ->
// Apply existing images if available
if (productImages.isNotEmpty()) {
adapter.updateProductImages(productImages)
}
}
}
isNestedScrollingEnabled = false
}

View File

@ -40,6 +40,11 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
private val _orderCreated = MutableLiveData<Boolean>()
val orderCreated: LiveData<Boolean> = _orderCreated
private val _productImages = MutableLiveData<Map<Int, String>>(emptyMap())
val productImages: LiveData<Map<Int, String>> = _productImages
private val currentImages = mutableMapOf<Int, String>()
// Initialize "Buy Now" checkout
fun initializeBuyNow(
storeId: Int,
@ -156,9 +161,13 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
)
Log.d(TAG, "CheckoutData initialized with ${matchingItems.size} items")
matchingItems.forEachIndexed { index, item ->
val isWholesale = isWholesaleMap[item.cartItemId] ?: false
Log.d(TAG, "Item $index: ${item.productName}, Price: ${item.price}, IsWholesale: $isWholesale")
matchingItems.forEach { item ->
Log.d("CheckoutViewModel", "About to load image for productId: ${item.productId}")
loadProductImage(item.productId)
}
matchingItems.forEach { item ->
loadProductImage(item.productId)
}
// Calculate totals with updated prices
@ -179,6 +188,8 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
fun getPaymentMethods() {
viewModelScope.launch {
try {
@ -417,6 +428,31 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
fun loadProductImage(productId: Int) {
Log.d("CheckoutViewModel", "loadProductImage called for productId: $productId")
viewModelScope.launch {
try {
Log.d("CheckoutViewModel", "Fetching product detail for productId: $productId")
val productDetail = repository.fetchProductDetail(productId)
Log.d("CheckoutViewModel", "Product detail result: $productDetail")
val imageUrl = productDetail?.product?.image
Log.d("CheckoutViewModel", "Extracted image URL: $imageUrl")
currentImages[productId] = imageUrl.toString()
Log.d("CheckoutViewModel", "Updated currentImages: $currentImages")
_productImages.postValue(currentImages.toMap())
Log.d("CheckoutViewModel", "Posted to _productImages LiveData")
} catch (e: Exception) {
Log.e("CheckoutViewModel", "Error loading image for productId $productId", e)
// fallback if error
currentImages[productId] = ""
_productImages.postValue(currentImages.toMap())
}
}
}
// Get shipping price
private fun getShippingPrice(): Double {
val data = _checkoutData.value ?: return 0.0
@ -432,3 +468,4 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
private const val TAG = "CheckoutViewModel"
}
}

View File

@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
@ -13,6 +14,8 @@ import java.util.Locale
class SingleCartItemAdapter(private val cartItem: CartItemsItem) :
RecyclerView.Adapter<SingleCartItemAdapter.CartItemViewHolder>() {
private var productImages: Map<Int, String> = emptyMap()
class CartItemViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
@ -24,16 +27,28 @@ class SingleCartItemAdapter(private val cartItem: CartItemsItem) :
override fun getItemCount(): Int = 1
fun updateProductImages(newImages: Map<Int, String>) {
productImages = newImages
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
with(holder.binding) {
// Set cart item details
tvProductName.text = cartItem.productName
tvProductQuantity.text = "${cartItem.quantity} buah"
tvProductPrice.text = formatCurrency(cartItem.price.toDouble())
// Load placeholder image
// Get the image for this product
val img = productImages[cartItem.productId]
val fullImageUrl = when (img) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> null
}
Glide.with(ivProduct.context)
.load(R.drawable.placeholder_image)
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(ivProduct)

View File

@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
@ -36,9 +37,16 @@ class SingleProductAdapter(private val checkoutData: CheckoutData) :
tvProductPrice.text = formatCurrency(checkoutData.productPrice)
val fullImageUrl = when (val img = checkoutData.productImageUrl) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> null
}
// Load product image
Glide.with(ivProduct.context)
.load(checkoutData.productImageUrl)
.load(fullImageUrl)
.apply(
RequestOptions()
.placeholder(R.drawable.placeholder_image)

View File

@ -34,6 +34,7 @@ import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.applyLiveCounter
class AddAddressActivity : AppCompatActivity() {
private lateinit var binding: ActivityAddAddressBinding
@ -65,6 +66,12 @@ class AddAddressActivity : AppCompatActivity() {
binding = ActivityAddAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
applyLiveCounter(
binding.etDetailAlamat,
binding.tvCountDetail,
binding.tvCountDetailMax
)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager

View File

@ -33,6 +33,7 @@ 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.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
@ -166,6 +167,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
// Submit button
binding.btnSubmit.setOnClickListener {
validateAndUpload()
Log.d(TAG, "AddEvidencePaymentActivity onCreate completed")
}
@ -220,8 +222,6 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
val adapter = object : ArrayAdapter<String>(this, R.layout.item_dialog_add_evidence, R.id.tvOption, options) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
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
}
}
@ -313,12 +313,14 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
return
}
//in case applied metode pembayaran yang lain
// if (binding.spinnerPaymentMethod.selectedItemPosition == 0) {
// Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
// return
// }
binding.etAccountNumber.visibility = View.GONE
//in case applied nomor rekening
// if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
// Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show()
// return
@ -330,8 +332,16 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
// }
// All validations passed, proceed with upload
uploadPaymentProof()
PopUpDialog.showConfirmDialog(
context = this,
title = "Apakah bukti yang dikirimkan sudah benar?",
message = "Pastikan bukti yang dikirimkan valid",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
uploadPaymentProof()
}
)
}
private fun uploadPaymentProof() {
@ -443,9 +453,6 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
).show()
}
companion object {
private const val PERMISSION_REQUEST_CODE = 100
private const val TAG = "AddEvidenceActivity"

View File

@ -232,7 +232,6 @@ class PaymentActivity : AppCompatActivity() {
else -> emptyList()
}
// Tampilkan instruksi dalam dialog
val dialog = AlertDialog.Builder(this)
.setTitle("Petunjuk Transfer $type")
.setItems(instructions.toTypedArray(), null)

View File

@ -57,6 +57,14 @@ class HistoryActivity : AppCompatActivity() {
}
}
// override fun onDialogConfirmed() {
// // Option 1: refresh activity
// recreate()
//
// // Or Option 2: reload only data
// // viewModel.loadOrders()
// }
private fun setupToolbar() {
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)

View File

@ -231,19 +231,21 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
private suspend fun refresh(status: String) {
fun refresh(status: String) {
Log.d(TAG, "⏳ refresh(\"$status\") started")
try {
if (status == "all") {
Log.d(TAG, "🌐 Calling getAllOrdersCombined()")
getAllOrdersCombined() // network → cache
} else {
Log.d(TAG, "🌐 repository.getOrderList(\"$status\")")
repository.getOrderList(status) // network → cache
viewModelScope.launch {
if (status == "all") {
Log.d(TAG, "🌐 Calling getAllOrdersCombined()")
getAllOrdersCombined() // network → cache
} else {
Log.d(TAG, "🌐 repository.getOrderList(\"$status\")")
repository.getOrderList(status) // network → cache
}
Log.d(TAG, "✅ refresh(\"$status\") completed (repository updated)")
// Flow that watches DB/cache will emit automatically
}
Log.d(TAG, "✅ refresh(\"$status\") completed (repository updated)")
// Flow that watches DB/cache will emit automatically
} catch (e: Exception) {
Log.e(TAG, "❌ refresh(\"$status\") failed: ${e.message}", e)
}

View File

@ -28,6 +28,7 @@ import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity
import com.alya.ecommerce_serang.ui.order.history.cancelorder.CancelOrderBottomSheet
import com.alya.ecommerce_serang.ui.order.review.CreateReviewActivity
import com.alya.ecommerce_serang.ui.product.ReviewProductActivity
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
@ -41,7 +42,8 @@ import java.util.TimeZone
class OrderHistoryAdapter(
private val onOrderClickListener: (OrdersItem) -> Unit,
private val viewModel: HistoryViewModel,
private val callbacks: OrderActionCallbacks
private val callbacks: OrderActionCallbacks,
private val listener: OnDialogActionListener
) : RecyclerView.Adapter<OrderHistoryAdapter.OrderViewHolder>() {
interface OrderActionCallbacks {
@ -237,9 +239,18 @@ class OrderHistoryAdapter(
callbacks.onShowLoading(true)
// Call ViewModel
viewModel.confirmOrderCompleted(order.orderId, "completed")
PopUpDialog.showConfirmDialog(
context = itemView.context,
title = "Apakah anda yakin pesanan sudah sampai?",
message = "Pastikan pesanan sudah samapi di alamat tujuan",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
viewModel.confirmOrderCompleted(order.orderId, "completed")
listener.onDialogConfirmed()
}
)
// viewModel.refreshOrders()
}
}
@ -520,9 +531,19 @@ class OrderHistoryAdapter(
val bottomSheet = CancelOrderBottomSheet(
orderId = orderId,
onOrderCancelled = {
callbacks.onOrderCancelled(orderId.toString(), true, "Order cancelled successfully")
// Show a success message
Toast.makeText(context, "Pesanan berhasil dibatalkan", Toast.LENGTH_SHORT).show()
PopUpDialog.showConfirmDialog(
context = itemView.context,
title = "Apakah anda yakin ingin membatalkan pesanan?",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
callbacks.onOrderCancelled(orderId.toString(), true, "Order cancelled successfully")
// Show a success message
Toast.makeText(context, "Pesanan berhasil dibatalkan", Toast.LENGTH_SHORT).show()
listener.onDialogConfirmed()
}
)
}
)
@ -603,3 +624,7 @@ class OrderHistoryAdapter(
}
}
}
interface OnDialogActionListener {
fun onDialogConfirmed()
}

View File

@ -24,7 +24,7 @@ import com.alya.ecommerce_serang.ui.order.history.detailorder.DetailOrderStatusA
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks, OnDialogActionListener {
private var _binding: FragmentOrderListBinding? = null
private val binding get() = _binding!!
@ -93,7 +93,8 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
navigateToOrderDetail(order)
},
viewModel = viewModel,
callbacks = this // Pass this fragment as callback
callbacks = this,
listener = this// Pass this fragment as callback
)
orderAdapter.setFragmentStatus(status)
@ -175,7 +176,6 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
override fun onOrderCancelled(orderId: String, success: Boolean, message: String) {
if (success) {
Toast.makeText(requireContext(), "Berhasil batalkan pesanan", Toast.LENGTH_SHORT).show()
Log.d("OrderListFragment", "Order cancel success: $message")
// loadOrders() // Refresh the list
if (success) viewModel.updateStatus(status, forceRefresh = true)
@ -210,4 +210,14 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
super.onResume()
observeOrderList()
}
override fun onDialogConfirmed() {
viewModel.refresh(status)
// Option 1: refresh seluruh fragment
requireActivity().supportFragmentManager.beginTransaction()
.detach(this)
.attach(this)
.commit()
}
}

View File

@ -130,13 +130,6 @@ class CancelOrderBottomSheet(
is Result.Success -> {
// Hide loading indicator
showLoading(false)
// Show success message
Toast.makeText(
context,
"Pesanan berhasil dibatalkan",
Toast.LENGTH_SHORT
).show()
Log.d(TAG, "Cancel order status: SUCCESS, message: ${result.data.message}")
// Notify callback and close dialog

View File

@ -742,7 +742,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
// Output format
val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale("id", "ID"))
val outputFormat = SimpleDateFormat("dd MMM yyyy", Locale("id", "ID"))
// Parse the input date
val date = inputFormat.parse(dateString)

View File

@ -88,13 +88,14 @@ class CategoryProductsActivity : AppCompatActivity() {
setSupportActionBar(toolbar)
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
// title = category.name
title = ""
}
val fullImageUrl = if (category.image.startsWith("/")) {
BASE_URL + category.image.removePrefix("/") // Append base URL if the path starts with "/"
} else {
category.image // Use as is if it's already a full URL
val fullImageUrl = when (val img = category.image) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> null
}
// Load category image

View File

@ -3,7 +3,7 @@ package com.alya.ecommerce_serang.ui.product.category
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.databinding.ItemProductGridBinding
@ -46,8 +46,15 @@ class ProductsCategoryAdapter(
val priceValue = product.price.toDoubleOrNull() ?: 0.0
tvProductPrice.text = "Rp ${NumberFormat.getNumberInstance(Locale("id", "ID")).format(priceValue.toInt())}"
// Load product image
val fullImageUrl = when (val img = product.image) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> null
}
Glide.with(itemView.context)
.load("${BuildConfig.BASE_URL}${product.image}")
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.centerCrop()
@ -57,15 +64,6 @@ class ProductsCategoryAdapter(
root.setOnClickListener {
onClick(product)
}
// // Optional: Show stock status
// if (product.stock > 0) {
// tvStockStatus.text = "Stock: ${product.stock}"
// tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.green))
// } else {
// tvStockStatus.text = "Out of Stock"
// tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.red))
// }
}
}
}

View File

@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.ui.profile
import android.app.AlertDialog
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
@ -27,6 +26,7 @@ import com.alya.ecommerce_serang.ui.profile.mystore.RegisterStoreActivity
import com.alya.ecommerce_serang.ui.profile.mystore.StoreOnReviewActivity
import com.alya.ecommerce_serang.ui.profile.mystore.StoreSuspendedActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
@ -200,14 +200,16 @@ class ProfileFragment : Fragment() {
private fun logout(){
AlertDialog.Builder(requireContext())
.setTitle("Konfirmasi")
.setMessage("Apakah anda yakin ingin keluar?")
.setPositiveButton("Ya") { _, _ ->
PopUpDialog.showConfirmDialog(
context = requireContext(),
title = "Konfirmasi",
message = "Apakah anda yakin ingin keluar?",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
actionLogout()
}
.setNegativeButton("Tidak", null)
.show()
)
}
private fun actionLogout(){
@ -222,6 +224,7 @@ class ProfileFragment : Fragment() {
delay(500)
loadingDialog.dismiss()
sessionManager.clearAll()
viewModel.deleteFCM()
val intent = Intent(requireContext(), LoginActivity::class.java)
startActivity(intent)
requireActivity().finish()

View File

@ -2,7 +2,6 @@ package com.alya.ecommerce_serang.ui.profile.editprofile
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.app.DatePickerDialog
import android.content.Intent
import android.content.pm.PackageManager
@ -29,6 +28,7 @@ import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityEditProfileCustBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
import com.bumptech.glide.Glide
@ -213,12 +213,17 @@ class EditProfileCustActivity : AppCompatActivity() {
}
private fun confirmUpdate() {
AlertDialog.Builder(this)
.setTitle("Konfirmasi Perubahan")
.setMessage("Apakah Anda yakin ingin menyimpan perubahan profil toko Anda?")
.setPositiveButton("Ya") { _, _ -> saveProfile() }
.setNegativeButton("Batal", null)
.show()
PopUpDialog.showConfirmDialog(
context = this,
title = "Apakah Anda yakin ingin menyimpan perubahan profil?",
message = "Pastikan data yang dimasukkan sudah benar",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
saveProfile()
}
)
}
private fun hasChanged(): Boolean {

View File

@ -51,7 +51,6 @@ class MyStoreActivity : AppCompatActivity() {
enableEdgeToEdge()
binding.headerMyStore.headerTitle.text = "Toko Saya"
binding.headerMyStore.headerLeftIcon.setOnClickListener {
@ -107,22 +106,23 @@ class MyStoreActivity : AppCompatActivity() {
}
binding.tvHistory.setOnClickListener {
startActivity(Intent(this, SellsActivity::class.java))
//startActivity(Intent(this, SellsActivity::class.java))
startSellsActivityWithStatus("all")
}
binding.layoutPerluTagihan.setOnClickListener {
startActivity(Intent(this, SellsActivity::class.java))
//navigateToSellsFragment("pending")
//startActivity(Intent(this, SellsActivity::class.java))
startSellsActivityWithStatus("unpaid")
}
binding.layoutPembayaran.setOnClickListener {
startActivity(Intent(this, SellsActivity::class.java))
//navigateToSellsFragment("paid")
//startActivity(Intent(this, SellsActivity::class.java))
startSellsActivityWithStatus("paid")
}
binding.layoutPerluDikirim.setOnClickListener {
startActivity(Intent(this, SellsActivity::class.java))
//navigateToSellsFragment("processed")
//startActivity(Intent(this, SellsActivity::class.java))
startSellsActivityWithStatus("processed")
}
binding.layoutProductMenu.setOnClickListener {
@ -206,6 +206,12 @@ class MyStoreActivity : AppCompatActivity() {
}
}
private fun startSellsActivityWithStatus(status: String?) {
val intent = Intent(this, SellsActivity::class.java)
intent.putExtra(SellsActivity.EXTRA_INITIAL_STATUS, status)
startActivity(intent)
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {

View File

@ -42,6 +42,7 @@ import com.alya.ecommerce_serang.utils.ImageUtils
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.RegisterStoreViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.applyLiveCounter
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -102,6 +103,30 @@ class RegisterStoreActivity : AppCompatActivity() {
binding = ActivityRegisterStoreBinding.inflate(layoutInflater)
setContentView(binding.root)
applyLiveCounter(
binding.etStoreName,
binding.tvCountName,
binding.tvCountNameMax
)
applyLiveCounter(
binding.etStoreDescription,
binding.tvCountDesc,
binding.tvCountDescMax
)
applyLiveCounter(
binding.etStreet,
binding.tvCountStreet,
binding.tvCountStreetMax
)
applyLiveCounter(
binding.etAddressDetail,
binding.tvCountAddressDetail,
binding.tvCountAddressDetailMax
)
sessionManager = SessionManager(this)
WindowCompat.setDecorFitsSystemWindows(window, false)

View File

@ -24,6 +24,7 @@ import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.utils.ImageUtils.compressImage
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -374,14 +375,11 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Show a dialog with the success message
runOnUiThread {
AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Berhasil")
.setMessage(successMessage)
.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
finish()
}
.show()
PopUpDialog.showConfirmDialog(
context = this@BalanceTopUpActivity,
iconRes = R.drawable.checkmark__1_,
title = "Berhasil melakukan Top-Up"
)
}
} else {
// Get more detailed error information
@ -408,13 +406,12 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Show a dialog with the error message
runOnUiThread {
AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Error Response")
.setMessage(errorMessage)
.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
}
.show()
PopUpDialog.showConfirmDialog(
context = this@BalanceTopUpActivity,
iconRes = R.drawable.ic_cancel,
title = "Gagal melakukan Top-Up"
)
}
}
} catch (e: Exception) {

View File

@ -31,6 +31,7 @@ import com.alya.ecommerce_serang.utils.CompressionResult
import com.alya.ecommerce_serang.utils.FileUtils.compressFileToMax1MB
import com.alya.ecommerce_serang.utils.ImageUtils.compressImage
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.applyLiveCounter
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
import com.bumptech.glide.Glide
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -116,6 +117,18 @@ class DetailStoreProductActivity : AppCompatActivity() {
binding = ActivityDetailStoreProductBinding.inflate(layoutInflater)
setContentView(binding.root)
applyLiveCounter(
binding.edtNamaProduk,
binding.tvCountName,
binding.tvCountNameMax
)
applyLiveCounter(
binding.edtDeskripsiProduk,
binding.tvCountDesc,
binding.tvCountDescMax
)
isEditing = intent.getBooleanExtra("is_editing", false)
productId = intent.getIntExtra("product_id", -1)
@ -271,6 +284,9 @@ class DetailStoreProductActivity : AppCompatActivity() {
binding.switcherHalal.showNext()
}
binding.switchIsActive.isChecked = product.status == "active"
binding.switchIsActive.jumpDrawablesToCurrentState()
validateForm()
}
@ -418,8 +434,8 @@ class DetailStoreProductActivity : AppCompatActivity() {
Log.d(TAG, "SPPIRT URI: ${sppirtUri.toString()}")
Log.d(TAG, "Halal URI: ${halalUri.toString()}")
logFileInfo("Sppirt Size", sppirtFile!!)
logFileInfo("Halal Size", halalFile!!)
sppirtFile?.let { logFileInfo("Sppirt Size", it) }
halalFile?.let { logFileInfo("Halal Size", it) }
val imagePart = imageFile?.let { createPartFromFile("productimg", it) }
val sppirtPart = sppirtFile?.let { createPartFromFile("sppirt", it) }

View File

@ -29,9 +29,10 @@ import com.alya.ecommerce_serang.databinding.DialogStoreImageBinding
import com.alya.ecommerce_serang.ui.profile.mystore.profile.address.DetailStoreAddressActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info.PaymentInfoActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service.ShippingServiceActivity
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.PopUpDialog
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.bumptech.glide.Glide
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
@ -39,7 +40,6 @@ import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.io.FileOutputStream
import kotlin.getValue
class DetailStoreProfileActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailStoreProfileBinding
@ -244,12 +244,17 @@ class DetailStoreProfileActivity : AppCompatActivity() {
}
private fun confirmUpdate() {
AlertDialog.Builder(this)
.setTitle("Konfirmasi Perubahan")
.setMessage("Apakah Anda yakin ingin menyimpan perubahan profil toko Anda?")
.setPositiveButton("Ya") { _, _ -> updateStoreProfile() }
.setNegativeButton("Batal", null)
.show()
PopUpDialog.showConfirmDialog(
context = this,
title = "Apakah Anda yakin ingin menyimpan perubahan profil?",
message = "Pastikan data yang dimasukkan sudah benar",
positiveText = "Ya",
negativeText = "Tidak",
onYesClicked = {
updateStoreProfile()
}
)
}
private fun showImageOptions() {

View File

@ -22,6 +22,7 @@ import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreAddressBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.applyLiveCounter
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.google.android.material.snackbar.Snackbar
@ -55,6 +56,18 @@ class DetailStoreAddressActivity : AppCompatActivity() {
binding = ActivityDetailStoreAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
applyLiveCounter(
binding.edtStreet,
binding.tvCountStreet,
binding.tvCountStreetMax
)
applyLiveCounter(
binding.edtDetailAddress,
binding.tvCountDetail,
binding.tvCountDetailMax
)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)

View File

@ -2,7 +2,6 @@ package com.alya.ecommerce_serang.ui.profile.mystore.sells
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
@ -12,7 +11,6 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailSellsBinding
import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.DetailShipmentActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
@ -22,7 +20,6 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import kotlin.getValue
class DetailSellsActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailSellsBinding
@ -100,8 +97,9 @@ class DetailSellsActivity : AppCompatActivity() {
tvOrderCustomer.text = sell.username
tvOrderDate.text = formatDate(sell.updatedAt.toString())
tvOrderTotalProduct.text = "(${sell.orderItems?.size ?: 0} Barang)"
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
val totalPrice = (sell.totalAmount?.toDouble()?.toInt() ?: 0) - (sell.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvOrderSubtotal.text = formatPrice(totalPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderRecipient.text = sell.recipient ?: "-"
tvOrderRecipientNum.text = sell.receiptNum?.toString() ?: "-"

View File

@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.commit
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
@ -18,6 +19,9 @@ import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class SellsActivity : AppCompatActivity() {
companion object {
const val EXTRA_INITIAL_STATUS = "extra_initial_status"
}
private lateinit var binding: ActivitySellsBinding
private lateinit var sessionManager: SessionManager
@ -68,11 +72,18 @@ class SellsActivity : AppCompatActivity() {
onBackPressed()
finish()
}
// binding.edtSearch.doAfterTextChanged {
// val q = it?.toString()?.trim().orEmpty()
// (supportFragmentManager.findFragmentById(R.id.fragment_container_sells) as? SellsFragment)
// ?.onSearchQueryChanged(q)
// }
}
private fun showSellsFragment() {
val initialStatus = intent.getStringExtra(EXTRA_INITIAL_STATUS)
supportFragmentManager.commit {
replace(R.id.fragment_container_sells, SellsFragment())
replace(R.id.fragment_container_sells, SellsFragment.newInstance(initialStatus))
}
}
}

View File

@ -186,7 +186,9 @@ class SellsAdapter(
.into(ivSellsProduct)
tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsPrice.text = order.totalAmount?.let { formatPrice(it.toDouble().toInt()) }
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
Log.d("SellsAdapter", "Cek price:$totalPrice" )
}
"paid" -> {
layoutOrders.visibility = View.GONE
@ -206,7 +208,9 @@ class SellsAdapter(
tvSellsTitle.text = "Pesanan Telah Dibayar"
tvSellsDueDesc.text = "Konfirmasi pembayaran sebelum:"
tvSellsDue.text = formatDueDate(order.updatedAt.toString(), 2)
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
tvSellsQty.text = "${order.orderItems?.size} produk"
}
"processed" -> {
layoutOrders.visibility = View.GONE
@ -235,6 +239,10 @@ class SellsAdapter(
tvSellsLocation.text = order.subdistrict
tvSellsCustomer.text = order.username
tvSellsDue.text = formatDueDate(order.updatedAt.toString(), 2)
tvSellsQty.text = "${order.orderItems?.size} produk"
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
}
"shipped" -> {
layoutOrders.visibility = View.GONE
@ -246,7 +254,11 @@ class SellsAdapter(
tvSellsDue.text = formatDueDate(order.updatedAt.toString(), 0)
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
tvSellsQty.text = "${order.orderItems?.size} produk"
btnConfirmPayment.visibility = View.GONE
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
}
"delivered" -> {
layoutOrders.visibility = View.GONE
@ -257,8 +269,13 @@ class SellsAdapter(
tvSellsDueDesc.text = "Dikirimkan pada"
tvSellsDue.text = formatDueDate(order.updatedAt.toString(), 0)
tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
}
"completed" -> {
layoutOrders.visibility = View.GONE
@ -269,8 +286,13 @@ class SellsAdapter(
tvSellsDueDesc.text = "Selesai pada"
tvSellsDue.text = formatDueDate(order.updatedAt.toString(), 0)
tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
}
"canceled" -> {
layoutOrders.visibility = View.GONE
@ -281,6 +303,10 @@ class SellsAdapter(
tvSellsDueDesc.text = "Dibatalkan pada"
tvSellsDue.text = formatDueDate(order.updatedAt.toString(), 0)
tvSellsQty.text = "${order.orderItems?.size} produk"
val totalPrice = (order.totalAmount?.toDouble()?.toInt() ?: 0) - (order.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvSellsPrice.text =formatPrice(totalPrice).toString()
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
}

View File

@ -17,8 +17,19 @@ import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.android.material.tabs.TabLayoutMediator
class SellsFragment : Fragment() {
companion object {
private const val ARG_INITIAL_STATUS = "arg_initial_status"
fun newInstance(initialStatus: String?): SellsFragment =
SellsFragment().apply {
arguments = Bundle().apply {
putString(ARG_INITIAL_STATUS, initialStatus)
}
}
}
private var _binding: FragmentSellsBinding? = null
// private var currentSearchQuery = ""
private val binding get() = _binding!!
private lateinit var sessionManager: SessionManager
@ -45,6 +56,8 @@ class SellsFragment : Fragment() {
sessionManager = SessionManager(requireContext())
setupViewPager()
jumpToInitialStatusIfAny()
// binding.viewPagerSells.post { currentPage()?.filter(currentSearchQuery) }
}
private fun setupViewPager() {
@ -70,12 +83,60 @@ class SellsFragment : Fragment() {
statusPage()
}
private fun statusPage(){
private fun jumpToInitialStatusIfAny() {
val initial = arguments?.getString(ARG_INITIAL_STATUS)?.trim().orEmpty()
if (initial.isEmpty()) return
// Try adapters list first
var index = viewPagerAdapter.sellsStatuses.indexOf(initial)
// Fallback mapping (keeps working if the adapter changes order names)
if (index < 0) {
index = when (initial) {
"all" -> 0
"unpaid" -> 1
"paid" -> 2
"processed" -> 3
"shipped" -> 4
"delivered" -> 5
"completed" -> 6
"canceled" -> 7
else -> 0
}
}
if (index in 0 until (binding.viewPagerSells.adapter?.itemCount ?: 0)) {
// Ensure pager is ready, then jump without animation
binding.viewPagerSells.post {
binding.viewPagerSells.setCurrentItem(index, false)
// Make sure ViewModel filter matches the shown tab
sellsVm.updateStatus(
viewPagerAdapter.sellsStatuses.getOrNull(index) ?: initial,
forceRefresh = true
)
}
}
}
// fun onSearchQueryChanged(q: String) {
// currentSearchQuery = q
// currentPage()?.filter(q)
// }
// private fun currentPage(): SellsListFragment? {
// val pos = binding.viewPagerSells.currentItem
// val tag = "f${viewPagerAdapter.getItemId(pos)}" // requires stable ids in adapter (see step 4)
// return childFragmentManager.findFragmentByTag(tag) as? SellsListFragment
// }
private fun statusPage() {
binding.viewPagerSells.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val status = viewPagerAdapter.sellsStatuses[position]
sellsVm.updateStatus(status, forceRefresh = true)
// re-apply query when user switches tab
// currentPage()?.filter(currentSearchQuery)
}
}
)

View File

@ -25,6 +25,7 @@ import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.gson.Gson
import kotlinx.coroutines.launch
import java.util.Locale
class SellsListFragment : Fragment() {
@ -41,6 +42,13 @@ class SellsListFragment : Fragment() {
}
private lateinit var sellsAdapter: SellsAdapter
private var status: String = "all"
// private var allOrders: List<OrdersItem> = emptyList()
// private var currentQuery: String = ""
//
// fun filter(query: String) {
// currentQuery = query
// applyFilter()
// }
companion object {
private const val TAG = "SellsListFragment"
@ -89,6 +97,29 @@ class SellsListFragment : Fragment() {
observePaymentConfirmation()
}
// private fun applyFilter() {
// val q = currentQuery.lowercase(Locale.getDefault())
// val filtered = if (q.isBlank()) {
// allOrders
// } else {
// allOrders.filter { it.matches(q) }
// }
//
// sellsAdapter.submitList(filtered)
// binding.tvEmptyState.visibility = if (filtered.isEmpty()) View.VISIBLE else View.GONE
// binding.rvSells.visibility = if (filtered.isEmpty()) View.GONE else View.VISIBLE
// }
//
// private fun OrdersItem.matches(q: String): Boolean {
// val id = orderId?.toString()?.lowercase(Locale.getDefault()) ?: ""
// val customer = username?.lowercase(Locale.getDefault()) ?: ""
// val location = subdistrict?.lowercase(Locale.getDefault()) ?: ""
// val productHit = orderItems?.any { it?.productName
// ?.lowercase(Locale.getDefault())?.contains(q) == true } == true
//
// return id.contains(q) || customer.contains(q) || location.contains(q) || productHit
// }
private fun setupRecyclerView() {
Log.d(TAG, "Setting up RecyclerView")
sellsAdapter = SellsAdapter(
@ -118,6 +149,8 @@ class SellsListFragment : Fragment() {
is ViewState.Success -> {
binding.progressBar.visibility = View.GONE
Log.d(TAG, "Data received: ${result.data?.size ?: 0} items")
// allOrders = result.data ?: emptyList()
// applyFilter() // ← apply current query to fresh data
if (result.data.isNullOrEmpty()) {
binding.rvSells.visibility = View.GONE
@ -135,18 +168,17 @@ class SellsListFragment : Fragment() {
sellsAdapter.submitList(result.data)
Log.d(TAG, "Data submitted to adapter")
Log.d(TAG, "Adapter item count: ${sellsAdapter.itemCount}") }
Log.d(TAG, "Adapter item count: ${sellsAdapter.itemCount}")
}
}
is ViewState.Error -> {
Log.e(TAG, "❌ ViewState.Error received: ${result.message}")
binding.progressBar.visibility = View.GONE
binding.tvEmptyState.visibility = View.VISIBLE
Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
}
is ViewState.Loading -> {
binding.progressBar.visibility = View.VISIBLE
// Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
}
is ViewState.Loading -> binding.progressBar.visibility = View.VISIBLE
}
}
}
@ -213,7 +245,7 @@ class SellsListFragment : Fragment() {
override fun onResume() {
super.onResume()
viewModel.getSellList(status)
observeSellsList()
//observeSellsList()
}
override fun onDestroyView() {

View File

@ -26,4 +26,7 @@ class SellsViewPagerAdapter(
// Create a new instance of SellsListFragment with the appropriate status
return SellsListFragment.newInstance(sellsStatuses[position])
}
// override fun getItemId(position: Int): Long = sellsStatuses[position].hashCode().toLong()
// override fun containsItem(itemId: Long): Boolean =
// sellsStatuses.any { it.hashCode().toLong() == itemId }
}

View File

@ -145,7 +145,8 @@ class DetailPaymentActivity : AppCompatActivity() {
tvOrderCustomer.text = sell.username
tvOrderDate.text = formatDate(sell.updatedAt.toString())
tvOrderTotalProduct.text = "(${sell.orderItems?.size ?: 0} Barang)"
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
val totalPrice = (sell.totalAmount?.toDouble()?.toInt() ?: 0) - (sell.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvOrderSubtotal.text = formatPrice(totalPrice.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderDue.text = formatDueDate(sell.updatedAt.toString(), 2)

View File

@ -101,7 +101,8 @@ class DetailShipmentActivity : AppCompatActivity() {
tvOrderCustomer.text = sell.username
tvOrderDate.text = formatDate(sell.updatedAt.toString())
tvOrderTotalProduct.text = "(${sell.orderItems?.size ?: 0} Barang)"
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
val totalPrice = (sell.totalAmount?.toDouble()?.toInt() ?: 0) - (sell.shipmentPrice?.toDouble()?.toInt() ?: 0)
tvOrderSubtotal.text = formatPrice(totalPrice.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderDue.text = formatDueDate(sell.updatedAt.toString(), 2)

View File

@ -4,6 +4,9 @@ import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.widget.doAfterTextChanged
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.SellsRepository
@ -47,6 +50,8 @@ class ShipmentConfirmationActivity : AppCompatActivity() {
binding.edtKurir.setText(sells?.courier ?: "")
binding.edtLayananKirim.setText(sells?.service ?: "")
setupValidation()
binding.btnConfirm.setOnClickListener {
val receiptNum = binding.edtNoResi.text.toString().trim()
val orderId = sells?.orderId
@ -68,4 +73,32 @@ class ShipmentConfirmationActivity : AppCompatActivity() {
if (success) finish()
}
}
private fun setupValidation() {
// Re-validate whenever any field changes
listOf(
binding.edtKurir,
binding.edtLayananKirim,
binding.edtNoResi
).forEach { edit ->
edit.doAfterTextChanged { validateForm() }
}
// Initial state
validateForm()
}
private fun validateForm() {
val allFilled = binding.edtKurir.text?.toString()?.trim()?.isNotEmpty() == true &&
binding.edtLayananKirim.text?.toString()?.trim()?.isNotEmpty() == true &&
binding.edtNoResi.text?.toString()?.trim()?.isNotEmpty() == true
binding.btnConfirm.isEnabled = allFilled
binding.btnConfirm.setBackgroundResource(
if (allFilled) R.drawable.bg_button_active else R.drawable.bg_button_disabled
)
binding.btnConfirm.setTextColor(
ContextCompat.getColor(this, if (allFilled) R.color.white else R.color.black_300)
)
}
}

View File

@ -1,8 +1,12 @@
package com.alya.ecommerce_serang.utils
import android.os.Build
import android.text.InputFilter
import android.view.View
import android.view.WindowInsetsController
import android.widget.EditText
import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
fun Fragment.setLightStatusBar(){
@ -20,3 +24,26 @@ fun Fragment.setLightStatusBar(){
}
}
public fun applyLiveCounter(
editText: EditText,
tvCount: TextView,
tvMax: TextView
) {
val max = tvMax.text.toString().toIntOrNull() ?: Int.MAX_VALUE
// Replace any existing LengthFilter with the new max
val current = editText.filters?.toMutableList() ?: mutableListOf()
current.removeAll { it is InputFilter.LengthFilter }
current.add(InputFilter.LengthFilter(max))
editText.filters = current.toTypedArray()
// Set initial count (handles prefilled text / edit mode)
tvCount.text = (editText.text?.length ?: 0).toString()
// Update on change
editText.doAfterTextChanged {
val len = it?.length ?: 0
tvCount.text = len.toString()
}
}

View File

@ -1,22 +0,0 @@
package com.alya.ecommerce_serang.utils
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.annotation.DimenRes
import androidx.recyclerview.widget.RecyclerView
class HorizontalMarginItemDecoration(context: Context, @DimenRes horizontalMarginInDp: Int) :
RecyclerView.ItemDecoration() {
private val horizontalMarginInPx: Int =
context.resources.getDimension(horizontalMarginInDp).toInt()
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
) {
outRect.right = horizontalMarginInPx
outRect.left = horizontalMarginInPx
}
}

View File

@ -78,7 +78,6 @@ object PopUpDialog {
dialog.dismiss()
}
dialog.show()
}
}

View File

@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
import com.alya.ecommerce_serang.data.api.response.auth.DeleteFCMResponse
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
import com.alya.ecommerce_serang.data.repository.Result
@ -27,6 +28,10 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
private val _checkStore = MutableLiveData<Boolean>()
val checkStore: LiveData<Boolean> = _checkStore
private val _deleteFCMT = MutableLiveData<String>()
val deleteFCMT: LiveData<String> = _deleteFCMT
val changePasswordResult = MutableLiveData<Result<ChangePassResponse>>()
private val _logout = MutableLiveData<Boolean>()
val logout : LiveData<Boolean> = _logout
@ -61,7 +66,25 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
}
}
fun deleteFCM(){
viewModelScope.launch {
try {
// Call the repository function to request OTP
val response: DeleteFCMResponse = userRepository.deleteFCMToken()
// Log and store success message
Log.d("ProfileViewModel", "Has store: ${response.message}")
_deleteFCMT.postValue(response.message) // Store the message for UI feedback
} catch (exception: Exception) {
// Handle any errors and update state
_deleteFCMT.postValue(exception.message)
// Log the error for debugging
Log.e(":ProfileViewModel", "Error:", exception)
}
}
}
fun editProfileDirect(
context: Context,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -4,8 +4,8 @@
<!-- Logo with controlled size -->
<item
android:width="200dp"
android:height="200dp"
android:width="120dp"
android:height="120dp"
android:drawable="@drawable/logo_psb_crop"
android:gravity="center" />
</layer-list>

View File

@ -85,14 +85,45 @@
android:id="@+id/etDetailAlamat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:gravity="top"
android:hint="Isi detail alamat (nomor rumah, lantai, dll)"
android:inputType="textMultiLine"
android:lines="3"
android:padding="12dp"
android:textSize="14sp" />
android:background="@drawable/bg_text_field"
android:padding="8dp"
style="@style/body_small"
android:hint="Isi detail alamat di sini, contoh: Blok, No. Kavling, dsb."
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_detail_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="100"
android:textColor="@color/black_300"/>
</LinearLayout>
<!-- Provinsi -->
<TextView
@ -200,7 +231,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Desa"
android:text="Desa / Kelurahan"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
@ -312,7 +343,7 @@
<Button
android:id="@+id/btnReloadLocation"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="36dp"
android:text="Reload"
android:textSize="12sp"

View File

@ -28,10 +28,10 @@
app:layout_collapseMode="parallax"
android:contentDescription="Category Header Image" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blue_50" />
<!-- <View-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:background="@color/blue_50" />-->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"

View File

@ -411,11 +411,25 @@
<Button
android:id="@+id/btn_hold_payment"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:layout_weight="1"
android:scrollHorizontally="false"
android:singleLine="false"
style="@style/button.large.secondary.medium"
android:text="Tahan Konfirmasi"/>
<Button
android:id="@+id/btn_confirm_payment"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:layout_weight="1"
android:scrollHorizontally="false"
android:singleLine="false"
style="@style/button.large.active.medium"
android:text="Konfirmasi Terima"
android:layout_alignParentEnd="true"/>

View File

@ -205,7 +205,6 @@
</LinearLayout>
<!-- Jalan -->
<LinearLayout
android:layout_width="match_parent"
@ -223,13 +222,49 @@
<EditText
android:id="@+id/edt_street"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="70dp"
android:background="@drawable/bg_text_field"
android:hint="Isi nama jalan di sini"
android:padding="8dp"
style="@style/body_small"
android:hint="Isi nama jalan di sini"
android:inputType="text|textMultiLine"
android:gravity="top"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_street"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_street_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="255"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Kode Pos -->
@ -254,6 +289,7 @@
android:padding="8dp"
style="@style/body_small"
android:hint="Isi kode pos di sini"
android:inputType="number"
android:layout_marginTop="10dp"/>
</LinearLayout>
@ -275,15 +311,47 @@
<EditText
android:id="@+id/edt_detail_address"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi detail alamat di sini, contoh: Blok, No. Kavling, dsb."
android:padding="8dp"
style="@style/body_small"
android:inputType="text|textMultiLine"
android:gravity="top"
android:hint="Isi detail alamat di sini, contoh: Blok, No. Kavling, dsb."
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_detail_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="100"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Pinpoint Lokasi -->
@ -379,6 +447,10 @@
<Button
android:id="@+id/btn_save_address"
android:text="Simpan Perubahan"
android:layout_height="36dp"
android:layout_weight="1"
android:layout_gravity="center"
android:gravity="center"
style="@style/button.large.active.long"
android:enabled="true"
android:layout_marginBottom="16dp"/>

View File

@ -167,6 +167,40 @@
style="@style/body_small"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_name_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="50"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Kategori Produk -->
@ -271,6 +305,40 @@
android:gravity="top"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_desc_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="255"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Harga Produk -->

View File

@ -138,6 +138,40 @@
style="@style/body_small"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_name_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="30"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Deskripsi Toko -->
@ -166,6 +200,40 @@
android:gravity="top"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_desc_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="255"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Jenis UMKM -->
@ -435,7 +503,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:layout_marginVertical="24dp">
<LinearLayout
android:layout_width="match_parent"
@ -463,13 +531,49 @@
<EditText
android:id="@+id/et_street"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="70dp"
android:background="@drawable/bg_text_field"
android:hint="Isi jalan tempat toko Anda di sini"
android:padding="8dp"
style="@style/body_small"
android:inputType="text|textMultiLine"
android:gravity="top"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_street"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_street_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="255"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Kode Pos -->
@ -532,15 +636,49 @@
<EditText
android:id="@+id/et_address_detail"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi detail alamat toko Anda di sini"
android:padding="8dp"
style="@style/body_small"
android:inputType="text|textMultiLine"
android:inputType="text"
android:gravity="top"
android:layout_marginTop="10dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tv_count_address_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="0"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="/"
android:layout_marginEnd="2dp"
android:textColor="@color/black_300"/>
<TextView
android:id="@+id/tv_count_address_detail_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:text="100"
android:textColor="@color/black_300"/>
</LinearLayout>
</LinearLayout>
<!-- Nama Bank-->
@ -626,7 +764,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12. Nama Pemilik Rekening"
android:text="11. Nama Pemilik Rekening"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -668,7 +806,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="11. Nomor Rekening"
android:text="12. Nomor Rekening"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -887,7 +1025,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="17. Dokumen NPWP"
android:text="16. Dokumen NPWP"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -954,7 +1092,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="16. Dokumen NIB"
android:text="17. Dokumen NIB"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -1015,7 +1153,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="17. Pilih Titik Lokasi Usaha"
android:text="18. Pilih Titik Lokasi Usaha"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>

View File

@ -17,7 +17,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/white">
android:background="@color/white"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"

View File

@ -12,11 +12,13 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<ImageView
android:id="@+id/dialogIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_gravity="center_horizontal"
android:contentDescription="icon dialog"
android:visibility="gone" />
@ -26,9 +28,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Judul"
android:layout_marginTop="16dp"
android:layout_marginTop="32dp"
android:paddingHorizontal="16dp"
android:layout_marginHorizontal="32dp"
android:paddingTop="4dp"
android:ellipsize="end"
android:scrollHorizontally="false"
android:singleLine="false"
android:gravity="center"
android:textSize="18sp"
android:maxLines="3"
android:fontFamily="@font/dmsans_semibold"
android:textColor="?attr/colorOnSurface" />
@ -37,7 +46,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginHorizontal="32dp"
android:gravity="center"
android:maxLines="3"
android:paddingHorizontal="16dp"
android:paddingTop="4dp"
android:ellipsize="end"
android:scrollHorizontally="false"
android:singleLine="false"
android:text="Pesan Dialog"
android:fontFamily="@font/dmsans_regular"
android:textSize="14sp"
@ -49,6 +65,7 @@
android:layout_marginTop="20dp"
android:gravity="center"
android:layout_marginBottom="16dp"
android:paddingHorizontal="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
@ -57,6 +74,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_regular"
android:theme="@style/body_medium"
android:text="Tidak" />
<com.google.android.material.button.MaterialButton
@ -66,6 +84,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:fontFamily="@font/dmsans_regular"
android:theme="@style/body_medium"
android:text="Ya" />
</LinearLayout>

View File

@ -37,6 +37,7 @@
android:background="@null"
android:hint="Isi stok produk di sini"
android:inputType="number"
android:textAlignment="center"
android:padding="8dp"
style="@style/body_small" />

View File

@ -25,7 +25,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Gracia Hotmauli"
android:text="Nama Pengguna"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/profileImage"
@ -35,7 +35,7 @@
android:id="@+id/tvUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="\@gracia34"
android:text="Username"
android:textColor="#757575"
app:layout_constraintStart_toStartOf="@id/tvName"
app:layout_constraintTop_toBottomOf="@id/tvName" />

View File

@ -3,8 +3,6 @@
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">
@ -18,17 +16,10 @@
android:id="@+id/tvOption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_semibold"
android:fontFamily="@font/dmsans_regular"
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

@ -133,23 +133,36 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_left"
android:layout_width="wrap_content"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_button_outline"
android:backgroundTint="@color/white"
android:maxLines="2"
android:ellipsize="end"
android:scrollHorizontally="false"
android:singleLine="false"
android:padding="4dp"
android:visibility="gone"
android:text="Tidak"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvDeadlineDate"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_right"
android:layout_width="wrap_content"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_button_filled"
android:maxLines="2"
android:ellipsize="end"
android:visibility="gone"
android:scrollHorizontally="false"
android:singleLine="false"
android:padding="4dp"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Kirim Bukti Bayar"
android:text="Ya"
app:layout_constraintTop_toBottomOf="@id/tvDeadlineDate"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>

View File

@ -39,7 +39,7 @@
<TextView
android:id="@+id/courier_name_cost"
android:fontFamily="@font/dmsans_semibold"
android:textSize="14sp"
android:textSize="16sp"
android:paddingHorizontal="2dp"
android:paddingTop="4dp"
android:ellipsize="end"
@ -54,7 +54,7 @@
android:id="@+id/est_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textSize="14sp"
android:paddingHorizontal="4dp"
android:text="Estimasi 3-4 hari"/>
</LinearLayout>
@ -65,7 +65,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:textSize="14sp"
android:textSize="16sp"
android:gravity="start"
android:fontFamily="@font/dmsans_semibold"
android:text="Rp15.000"/>

View File

@ -188,7 +188,7 @@
</style>
<style name="button.large.active.long">
<item name="android:layout_width">380dp</item>
<item name="android:layout_width">320dp</item>
</style>
<style name="button.large.active.medium">
@ -338,7 +338,6 @@
<style name="ThemeOverlay.MyApp.AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<!-- Rounded corners -->
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.MyApp.MediumComponent</item>
<!-- Dialog background color -->
<item name="cardBackgroundColor">@color/white</item>
<item name="materialAlertDialogBodyTextStyle">@font/dmsans_regular</item>
<item name="android:windowBackground">@android:color/transparent</item>