mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
add
This commit is contained in:
@ -18,8 +18,8 @@
|
||||
<application
|
||||
android:name=".app.App"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@ -30,9 +30,11 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.chat.ChatActivity"
|
||||
android:name=".ui.cart.MainActivity"
|
||||
android:exported="false" />
|
||||
<!-- <provider -->
|
||||
<activity
|
||||
android:name=".ui.chat.ChatActivity"
|
||||
android:exported="false" /> <!-- <provider -->
|
||||
<!-- android:name="androidx.startup.InitializationProvider" -->
|
||||
<!-- android:authorities="${applicationId}.androidx-startup" -->
|
||||
<!-- tools:node="remove" /> -->
|
||||
@ -55,7 +57,7 @@
|
||||
android:name=".ui.order.detail.PaymentActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".data.api.response.customer.cart.CartActivity"
|
||||
android:name=".ui.cart.CartActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.address.EditAddressActivity"
|
||||
|
@ -0,0 +1,14 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ReviewProductItem (
|
||||
@SerializedName("order_item_id")
|
||||
val orderItemId : Int,
|
||||
|
||||
@SerializedName("rating")
|
||||
val rating : Int,
|
||||
|
||||
@SerializedName("review_text")
|
||||
val reviewTxt : String
|
||||
)
|
@ -1,21 +0,0 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.customer.cart
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.alya.ecommerce_serang.R
|
||||
|
||||
class CartActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.activity_cart)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.customer.cart
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DeleteCartResponse(
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
@ -4,14 +4,14 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ListCartResponse(
|
||||
|
||||
@field:SerializedName("data")
|
||||
val data: List<DataItem>,
|
||||
@field:SerializedName("data")
|
||||
val data: List<DataItemCart>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class DataItem(
|
||||
data class DataItemCart(
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.customer.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateReviewResponse(
|
||||
|
||||
@field:SerializedName("order_item_id")
|
||||
val orderItemId: Int,
|
||||
|
||||
@field:SerializedName("rating")
|
||||
val rating: Int,
|
||||
|
||||
@field:SerializedName("review_text")
|
||||
val reviewText: String
|
||||
)
|
@ -11,6 +11,7 @@ import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.SearchRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
||||
@ -22,10 +23,12 @@ import com.alya.ecommerce_serang.data.api.response.chat.ChatListResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.chat.SendChatResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.chat.UpdateChatResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DeleteCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse
|
||||
@ -197,6 +200,11 @@ interface ApiService {
|
||||
@Body updateCart: UpdateCart
|
||||
): Response<UpdateCartResponse>
|
||||
|
||||
@DELETE("cart/delete/{id}")
|
||||
suspend fun deleteCart(
|
||||
@Path("id") cartItemId : Int
|
||||
):Response<DeleteCartResponse>
|
||||
|
||||
@POST("couriercost")
|
||||
suspend fun countCourierCost(
|
||||
@Body courierCost : CourierCostRequest
|
||||
@ -232,6 +240,11 @@ interface ApiService {
|
||||
@Part complaintimg: MultipartBody.Part
|
||||
): Response<ComplaintResponse>
|
||||
|
||||
@POST("review")
|
||||
suspend fun createReview(
|
||||
@Body contentReview : ReviewProductItem
|
||||
): Response<CreateReviewResponse>
|
||||
|
||||
@POST("search")
|
||||
suspend fun saveSearchQuery(
|
||||
@Body searchRequest: SearchRequest
|
||||
|
@ -2,28 +2,30 @@ package com.alya.ecommerce_serang.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -155,7 +157,7 @@ class OrderRepository(private val apiService: ApiService) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCart(): Result<List<DataItem>> {
|
||||
suspend fun getCart(): Result<List<DataItemCart>> {
|
||||
return try {
|
||||
val response = apiService.getCart()
|
||||
|
||||
@ -178,6 +180,42 @@ class OrderRepository(private val apiService: ApiService) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
suspend fun updateCart(updateCart: UpdateCart): Result<String> {
|
||||
return try {
|
||||
val response = apiService.updateCart(updateCart)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Result.Success(response.body()?.message ?: "Cart updated successfully")
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Failed to update cart"
|
||||
Log.e("Order Repository", "Error updating cart: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception updating cart", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteCartItem(cartItemId: Int): Result<String> {
|
||||
return try {
|
||||
val response = apiService.deleteCart(cartItemId)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Result.Success(response.body()?.message ?: "Item removed from cart")
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Failed to remove item from cart"
|
||||
Log.e("Order Repository", "Error deleting cart item: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception deleting cart item", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchStoreDetail(storeId: Int): Result<StoreProduct?> {
|
||||
return try {
|
||||
val response = apiService.getDetailStore(storeId)
|
||||
|
@ -0,0 +1,185 @@
|
||||
package com.alya.ecommerce_serang.ui.cart
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityCartBinding
|
||||
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CartActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityCartBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var storeAdapter: StoreAdapter
|
||||
|
||||
private val viewModel: CartViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
CartViewModel(orderRepository)
|
||||
}
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityCartBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Apply insets to your root layout
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.setPadding(
|
||||
systemBars.left,
|
||||
systemBars.top,
|
||||
systemBars.right,
|
||||
systemBars.bottom
|
||||
)
|
||||
windowInsets
|
||||
}
|
||||
|
||||
setupRecyclerView()
|
||||
setupListeners()
|
||||
observeViewModel()
|
||||
viewModel.getCart()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
storeAdapter = StoreAdapter(
|
||||
onStoreCheckChanged = { storeId, isChecked ->
|
||||
if (isChecked) {
|
||||
viewModel.toggleStoreSelection(storeId)
|
||||
} else {
|
||||
viewModel.toggleStoreSelection(storeId)
|
||||
}
|
||||
},
|
||||
onItemCheckChanged = { cartItemId, storeId, isChecked ->
|
||||
viewModel.toggleItemSelection(cartItemId, storeId)
|
||||
},
|
||||
onItemQuantityChanged = { cartItemId, quantity ->
|
||||
viewModel.updateCartItem(cartItemId, quantity)
|
||||
},
|
||||
onItemDeleted = { cartItemId ->
|
||||
viewModel.deleteCartItem(cartItemId)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvCart.apply {
|
||||
layoutManager = LinearLayoutManager(this@CartActivity)
|
||||
adapter = storeAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
binding.cbSelectAll.setOnCheckedChangeListener { _, _ ->
|
||||
viewModel.toggleSelectAll()
|
||||
}
|
||||
|
||||
binding.btnCheckout.setOnClickListener {
|
||||
if (viewModel.totalSelectedCount.value ?: 0 > 0) {
|
||||
val selectedItems = viewModel.prepareCheckout()
|
||||
if (selectedItems.isNotEmpty()) {
|
||||
// Navigate to checkout
|
||||
val intent = Intent(this, CheckoutActivity::class.java)
|
||||
// You would pass the selected items to the next activity
|
||||
// intent.putExtra("SELECTED_ITEMS", selectedItems)
|
||||
startActivity(intent)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Pilih produk terlebih dahulu", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnShopNow.setOnClickListener {
|
||||
// Navigate to product listing/home
|
||||
//implement home or search activity
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeViewModel() {
|
||||
viewModel.cartItems.observe(this) { cartItems ->
|
||||
if (cartItems.isNullOrEmpty()) {
|
||||
showEmptyState(true)
|
||||
} else {
|
||||
showEmptyState(false)
|
||||
storeAdapter.submitList(cartItems)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
// Show/hide loading indicator if needed
|
||||
}
|
||||
|
||||
viewModel.errorMessage.observe(this) { errorMessage ->
|
||||
errorMessage?.let {
|
||||
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.totalPrice.observe(this) { totalPrice ->
|
||||
binding.tvTotalPrice.text = formatCurrency(totalPrice)
|
||||
}
|
||||
|
||||
viewModel.totalSelectedCount.observe(this) { count ->
|
||||
binding.btnCheckout.text = "Beli ($count)"
|
||||
}
|
||||
|
||||
viewModel.selectedItems.observe(this) { selectedItems ->
|
||||
viewModel.selectedStores.value?.let { selectedStores ->
|
||||
viewModel.activeStoreId.value?.let { activeStoreId ->
|
||||
storeAdapter.updateSelectedItems(selectedItems, selectedStores, activeStoreId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.allSelected.observe(this) { allSelected ->
|
||||
// Update the "select all" checkbox without triggering the listener
|
||||
val selectCbAll = binding.cbSelectAll
|
||||
selectCbAll.setOnCheckedChangeListener(null)
|
||||
selectCbAll.isChecked = allSelected
|
||||
selectCbAll.setOnCheckedChangeListener { _, _ ->
|
||||
viewModel.toggleSelectAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEmptyState(isEmpty: Boolean) {
|
||||
if (isEmpty) {
|
||||
binding.rvCart.visibility = View.GONE
|
||||
binding.emptyStateLayout.visibility = View.VISIBLE
|
||||
findViewById<ConstraintLayout>(R.id.bottomCheckoutLayout).visibility = View.GONE
|
||||
} else {
|
||||
binding.rvCart.visibility = View.VISIBLE
|
||||
binding.emptyStateLayout.visibility = View.GONE
|
||||
findViewById<ConstraintLayout>(R.id.bottomCheckoutLayout).visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Int): String {
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
|
||||
return format.format(amount).replace("Rp", "Rp ")
|
||||
}
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
package com.alya.ecommerce_serang.ui.cart
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CartViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
|
||||
private val _cartItems = MutableLiveData<List<DataItemCart>>()
|
||||
val cartItems: LiveData<List<DataItemCart>> = _cartItems
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
private val _errorMessage = MutableLiveData<String?>()
|
||||
val errorMessage: LiveData<String?> = _errorMessage
|
||||
|
||||
private val _totalPrice = MutableLiveData<Int>(0)
|
||||
val totalPrice: LiveData<Int> = _totalPrice
|
||||
|
||||
private val _selectedItems = MutableLiveData<HashSet<Int>>(HashSet())
|
||||
val selectedItems: LiveData<HashSet<Int>> = _selectedItems
|
||||
|
||||
private val _selectedStores = MutableLiveData<HashSet<Int>>(HashSet())
|
||||
val selectedStores: LiveData<HashSet<Int>> = _selectedStores
|
||||
|
||||
private val _totalSelectedCount = MutableLiveData<Int>(0)
|
||||
val totalSelectedCount: LiveData<Int> = _totalSelectedCount
|
||||
|
||||
// Track the currently active store ID for checkout
|
||||
private val _activeStoreId = MutableLiveData<Int?>(null)
|
||||
val activeStoreId: LiveData<Int?> = _activeStoreId
|
||||
|
||||
// Track if all items are selected
|
||||
private val _allSelected = MutableLiveData<Boolean>(false)
|
||||
val allSelected: LiveData<Boolean> = _allSelected
|
||||
|
||||
fun getCart() {
|
||||
_isLoading.value = true
|
||||
_errorMessage.value = null
|
||||
|
||||
viewModelScope.launch {
|
||||
when (val result = repository.getCart()) {
|
||||
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||
_cartItems.value = result.data
|
||||
_isLoading.value = false
|
||||
}
|
||||
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
||||
_errorMessage.value = result.exception.message
|
||||
_isLoading.value = false
|
||||
}
|
||||
is Result.Loading -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCartItem(cartItemId: Int, quantity: Int) {
|
||||
_isLoading.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val updateCart = UpdateCart(cartItemId, quantity)
|
||||
val result = repository.updateCart(updateCart)
|
||||
|
||||
if (result is com.alya.ecommerce_serang.data.repository.Result.Success) {
|
||||
// Refresh cart data after successful update
|
||||
getCart()
|
||||
calculateTotalPrice()
|
||||
} else {
|
||||
_errorMessage.value = (result as com.alya.ecommerce_serang.data.repository.Result.Error).exception.message
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = e.message
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCartItem(cartItemId: Int) {
|
||||
_isLoading.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = repository.deleteCartItem(cartItemId)
|
||||
|
||||
if (result is com.alya.ecommerce_serang.data.repository.Result.Success) {
|
||||
// Remove the item from selected items if it was selected
|
||||
val currentSelectedItems = _selectedItems.value ?: HashSet()
|
||||
if (currentSelectedItems.contains(cartItemId)) {
|
||||
currentSelectedItems.remove(cartItemId)
|
||||
_selectedItems.value = currentSelectedItems
|
||||
}
|
||||
|
||||
// Refresh cart data after successful deletion
|
||||
getCart()
|
||||
calculateTotalPrice()
|
||||
} else {
|
||||
_errorMessage.value = (result as Result.Error).exception.message
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = e.message
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleItemSelection(cartItemId: Int, storeId: Int) {
|
||||
val currentSelectedItems = _selectedItems.value ?: HashSet()
|
||||
val currentSelectedStores = _selectedStores.value ?: HashSet()
|
||||
|
||||
if (currentSelectedItems.contains(cartItemId)) {
|
||||
currentSelectedItems.remove(cartItemId)
|
||||
|
||||
// Check if there are no more selected items for this store
|
||||
val storeHasSelectedItems = _cartItems.value?.find { it.storeId == storeId }
|
||||
?.cartItems?.any { currentSelectedItems.contains(it.cartItemId) } ?: false
|
||||
|
||||
if (!storeHasSelectedItems) {
|
||||
currentSelectedStores.remove(storeId)
|
||||
|
||||
// If this was the active store, set active store to null
|
||||
if (_activeStoreId.value == storeId) {
|
||||
_activeStoreId.value = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there's an active store different from this item's store, deselect all items first
|
||||
if (_activeStoreId.value != null && _activeStoreId.value != storeId) {
|
||||
currentSelectedItems.clear()
|
||||
currentSelectedStores.clear()
|
||||
}
|
||||
|
||||
currentSelectedItems.add(cartItemId)
|
||||
currentSelectedStores.add(storeId)
|
||||
|
||||
// Set the active store
|
||||
_activeStoreId.value = storeId
|
||||
}
|
||||
|
||||
_selectedItems.value = currentSelectedItems
|
||||
_selectedStores.value = currentSelectedStores
|
||||
|
||||
calculateTotalPrice()
|
||||
updateTotalSelectedCount()
|
||||
checkAllSelected()
|
||||
}
|
||||
|
||||
fun toggleStoreSelection(storeId: Int) {
|
||||
val currentSelectedItems = _selectedItems.value ?: HashSet()
|
||||
val currentSelectedStores = _selectedStores.value ?: HashSet()
|
||||
val storeItems = _cartItems.value?.find { it.storeId == storeId }?.cartItems ?: emptyList()
|
||||
|
||||
if (currentSelectedStores.contains(storeId)) {
|
||||
// Deselect all items of this store
|
||||
currentSelectedStores.remove(storeId)
|
||||
storeItems.forEach { currentSelectedItems.remove(it.cartItemId) }
|
||||
|
||||
// If this was the active store, set active store to null
|
||||
if (_activeStoreId.value == storeId) {
|
||||
_activeStoreId.value = null
|
||||
}
|
||||
} else {
|
||||
// If there's another active store, deselect all items first
|
||||
if (_activeStoreId.value != null && _activeStoreId.value != storeId) {
|
||||
currentSelectedItems.clear()
|
||||
currentSelectedStores.clear()
|
||||
}
|
||||
|
||||
// Select all items of this store
|
||||
currentSelectedStores.add(storeId)
|
||||
storeItems.forEach { currentSelectedItems.add(it.cartItemId) }
|
||||
|
||||
// Set this as the active store
|
||||
_activeStoreId.value = storeId
|
||||
}
|
||||
|
||||
_selectedItems.value = currentSelectedItems
|
||||
_selectedStores.value = currentSelectedStores
|
||||
|
||||
calculateTotalPrice()
|
||||
updateTotalSelectedCount()
|
||||
checkAllSelected()
|
||||
}
|
||||
|
||||
fun toggleSelectAll() {
|
||||
val allItems = _cartItems.value ?: emptyList()
|
||||
val currentSelected = _allSelected.value ?: false
|
||||
|
||||
if (currentSelected) {
|
||||
// Deselect all
|
||||
_selectedItems.value = HashSet()
|
||||
_selectedStores.value = HashSet()
|
||||
_activeStoreId.value = null
|
||||
_allSelected.value = false
|
||||
} else {
|
||||
// If we have multiple stores, we need a special handling
|
||||
if (allItems.size > 1) {
|
||||
// Select all items from the first store only
|
||||
val firstStore = allItems.firstOrNull()
|
||||
if (firstStore != null) {
|
||||
val selectedItems = HashSet<Int>()
|
||||
firstStore.cartItems.forEach { selectedItems.add(it.cartItemId) }
|
||||
|
||||
_selectedItems.value = selectedItems
|
||||
_selectedStores.value = HashSet<Int>().apply { add(firstStore.storeId) }
|
||||
_activeStoreId.value = firstStore.storeId
|
||||
}
|
||||
} else {
|
||||
// Single store, select all items
|
||||
val selectedItems = HashSet<Int>()
|
||||
val selectedStores = HashSet<Int>()
|
||||
|
||||
allItems.forEach { dataItem ->
|
||||
selectedStores.add(dataItem.storeId)
|
||||
dataItem.cartItems.forEach { cartItem ->
|
||||
selectedItems.add(cartItem.cartItemId)
|
||||
}
|
||||
}
|
||||
|
||||
_selectedItems.value = selectedItems
|
||||
_selectedStores.value = selectedStores
|
||||
|
||||
if (allItems.isNotEmpty()) {
|
||||
_activeStoreId.value = allItems[0].storeId
|
||||
}
|
||||
}
|
||||
|
||||
_allSelected.value = true
|
||||
}
|
||||
|
||||
calculateTotalPrice()
|
||||
updateTotalSelectedCount()
|
||||
}
|
||||
|
||||
private fun calculateTotalPrice() {
|
||||
val selectedItems = _selectedItems.value ?: HashSet()
|
||||
var total = 0
|
||||
|
||||
_cartItems.value?.forEach { dataItem ->
|
||||
dataItem.cartItems.forEach { cartItem ->
|
||||
if (selectedItems.contains(cartItem.cartItemId)) {
|
||||
total += cartItem.price * cartItem.quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_totalPrice.value = total
|
||||
}
|
||||
|
||||
private fun updateTotalSelectedCount() {
|
||||
_totalSelectedCount.value = _selectedItems.value?.size ?: 0
|
||||
}
|
||||
|
||||
private fun checkAllSelected() {
|
||||
val allItems = _cartItems.value ?: emptyList()
|
||||
val selectedItems = _selectedItems.value ?: HashSet()
|
||||
|
||||
// If there are multiple stores, "all selected" is true only if all items of the active store are selected
|
||||
val activeStoreId = _activeStoreId.value
|
||||
val isAllSelected = if (activeStoreId != null) {
|
||||
val activeStoreItems = allItems.find { it.storeId == activeStoreId }?.cartItems ?: emptyList()
|
||||
activeStoreItems.all { selectedItems.contains(it.cartItemId) }
|
||||
} else {
|
||||
// No active store, so check if all items of any store are selected
|
||||
allItems.any { dataItem ->
|
||||
dataItem.cartItems.all { selectedItems.contains(it.cartItemId) }
|
||||
}
|
||||
}
|
||||
|
||||
_allSelected.value = isAllSelected
|
||||
}
|
||||
|
||||
fun prepareCheckout(): List<CartItemsItem> {
|
||||
val selectedItemsIds = _selectedItems.value ?: HashSet()
|
||||
val result = mutableListOf<CartItemsItem>()
|
||||
|
||||
_cartItems.value?.forEach { dataItem ->
|
||||
dataItem.cartItems.forEach { cartItem ->
|
||||
if (selectedItemsIds.contains(cartItem.cartItemId)) {
|
||||
result.add(cartItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
package com.alya.ecommerce_serang.ui.cart
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
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.R
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
|
||||
import com.bumptech.glide.Glide
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class StoreAdapter(
|
||||
private val onStoreCheckChanged: (Int, Boolean) -> Unit,
|
||||
private val onItemCheckChanged: (Int, Int, Boolean) -> Unit,
|
||||
private val onItemQuantityChanged: (Int, Int) -> Unit,
|
||||
private val onItemDeleted: (Int) -> Unit
|
||||
) : ListAdapter<DataItemCart, RecyclerView.ViewHolder>(StoreDiffCallback()) {
|
||||
|
||||
private var selectedItems = HashSet<Int>()
|
||||
private var selectedStores = HashSet<Int>()
|
||||
private var activeStoreId: Int? = null
|
||||
|
||||
companion object {
|
||||
private const val VIEW_TYPE_STORE = 0
|
||||
private const val VIEW_TYPE_ITEM = 1
|
||||
}
|
||||
|
||||
fun updateSelectedItems(selectedItems: HashSet<Int>, selectedStores: HashSet<Int>, activeStoreId: Int?) {
|
||||
this.selectedItems = selectedItems
|
||||
this.selectedStores = selectedStores
|
||||
this.activeStoreId = activeStoreId
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
var itemCount = 0
|
||||
for (store in currentList) {
|
||||
// Store header
|
||||
if (position == itemCount) {
|
||||
return VIEW_TYPE_STORE
|
||||
}
|
||||
itemCount++
|
||||
|
||||
// Check if position is in the range of this store's items
|
||||
if (position < itemCount + store.cartItems.size) {
|
||||
return VIEW_TYPE_ITEM
|
||||
}
|
||||
itemCount += store.cartItems.size
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
var count = 0
|
||||
for (store in currentList) {
|
||||
// One for store header
|
||||
count++
|
||||
// Plus the items in this store
|
||||
count += store.cartItems.size
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_STORE -> {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_store_cart, parent, false)
|
||||
StoreViewHolder(view)
|
||||
}
|
||||
VIEW_TYPE_ITEM -> {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_cart_product, parent, false)
|
||||
CartItemViewHolder(view)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid view type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val (storeIndex, itemIndex) = getStoreAndItemIndex(position)
|
||||
val store = currentList[storeIndex]
|
||||
|
||||
when (holder) {
|
||||
is StoreViewHolder -> {
|
||||
holder.bind(store, selectedStores.contains(store.storeId), activeStoreId == store.storeId) { isChecked ->
|
||||
onStoreCheckChanged(store.storeId, isChecked)
|
||||
}
|
||||
}
|
||||
is CartItemViewHolder -> {
|
||||
val cartItem = store.cartItems[itemIndex]
|
||||
val isSelected = selectedItems.contains(cartItem.cartItemId)
|
||||
val isEnabled = activeStoreId == null || activeStoreId == store.storeId
|
||||
|
||||
holder.bind(
|
||||
cartItem,
|
||||
isSelected,
|
||||
isEnabled,
|
||||
{ isChecked -> onItemCheckChanged(cartItem.cartItemId, store.storeId, isChecked) },
|
||||
{ quantity -> onItemQuantityChanged(cartItem.cartItemId, quantity) },
|
||||
{ onItemDeleted(cartItem.cartItemId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStoreAndItemIndex(position: Int): Pair<Int, Int> {
|
||||
var itemCount = 0
|
||||
for (storeIndex in currentList.indices) {
|
||||
// Store header position
|
||||
if (position == itemCount) {
|
||||
return Pair(storeIndex, -1)
|
||||
}
|
||||
itemCount++
|
||||
|
||||
// Check if position is in the range of this store's items
|
||||
val store = currentList[storeIndex]
|
||||
if (position < itemCount + store.cartItems.size) {
|
||||
return Pair(storeIndex, position - itemCount)
|
||||
}
|
||||
itemCount += store.cartItems.size
|
||||
}
|
||||
throw IllegalArgumentException("Invalid position")
|
||||
}
|
||||
|
||||
class StoreViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val cbStore: CheckBox = itemView.findViewById(R.id.cbStore)
|
||||
private val tvStoreName: TextView = itemView.findViewById(R.id.tvStoreName)
|
||||
|
||||
fun bind(store: DataItemCart, isChecked: Boolean, isActiveStore: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
||||
tvStoreName.text = store.storeName
|
||||
|
||||
// Set checkbox state without triggering listener
|
||||
cbStore.setOnCheckedChangeListener(null)
|
||||
cbStore.isChecked = isChecked
|
||||
|
||||
// Only enable checkbox if this store is active or no store is active
|
||||
cbStore.setOnCheckedChangeListener { _, isChecked ->
|
||||
onCheckedChange(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CartItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val cbItem: CheckBox = itemView.findViewById(R.id.cbItem)
|
||||
private val ivProduct: ImageView = itemView.findViewById(R.id.ivProduct)
|
||||
private val tvProductName: TextView = itemView.findViewById(R.id.tvProductName)
|
||||
private val tvPrice: TextView = itemView.findViewById(R.id.tvPrice)
|
||||
private val btnMinus: ImageButton = itemView.findViewById(R.id.btnMinus)
|
||||
private val tvQuantity: TextView = itemView.findViewById(R.id.tvQuantity)
|
||||
private val btnPlus: ImageButton = itemView.findViewById(R.id.btnPlus)
|
||||
private val quantityController: ConstraintLayout = itemView.findViewById(R.id.quantityController)
|
||||
|
||||
fun bind(
|
||||
cartItem: CartItemsItem,
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
onQuantityChanged: (Int) -> Unit,
|
||||
onDelete: () -> Unit
|
||||
) {
|
||||
tvProductName.text = cartItem.productName
|
||||
tvPrice.text = formatCurrency(cartItem.price)
|
||||
tvQuantity.text = cartItem.quantity.toString()
|
||||
|
||||
// Load product image
|
||||
Glide.with(itemView.context)
|
||||
.load("https://example.com/images/${cartItem.productId}.jpg") // Assume image URL based on product ID
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.error(R.drawable.placeholder_image)
|
||||
.into(ivProduct)
|
||||
|
||||
// Set checkbox state without triggering listener
|
||||
cbItem.setOnCheckedChangeListener(null)
|
||||
cbItem.isChecked = isChecked
|
||||
cbItem.isEnabled = isEnabled
|
||||
|
||||
cbItem.setOnCheckedChangeListener { _, isChecked ->
|
||||
onCheckedChange(isChecked)
|
||||
}
|
||||
|
||||
// Quantity control
|
||||
btnMinus.setOnClickListener {
|
||||
val currentQty = tvQuantity.text.toString().toInt()
|
||||
if (currentQty > 1) {
|
||||
val newQty = currentQty - 1
|
||||
tvQuantity.text = newQty.toString()
|
||||
onQuantityChanged(newQty)
|
||||
} else {
|
||||
// If quantity would be 0, delete the item
|
||||
onDelete()
|
||||
}
|
||||
}
|
||||
|
||||
btnPlus.setOnClickListener {
|
||||
val currentQty = tvQuantity.text.toString().toInt()
|
||||
val newQty = currentQty + 1
|
||||
tvQuantity.text = newQty.toString()
|
||||
onQuantityChanged(newQty)
|
||||
}
|
||||
|
||||
// Disable quantity controls if item is not from active store
|
||||
btnMinus.isEnabled = isEnabled
|
||||
btnPlus.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Int): String {
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
|
||||
return format.format(amount).replace("Rp", "Rp ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StoreDiffCallback : DiffUtil.ItemCallback<DataItemCart>() {
|
||||
override fun areItemsTheSame(oldItem: DataItemCart, newItem: DataItemCart): Boolean {
|
||||
return oldItem.storeId == newItem.storeId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DataItemCart, newItem: DataItemCart): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
|
||||
import com.alya.ecommerce_serang.ui.cart.CartActivity
|
||||
import com.alya.ecommerce_serang.ui.notif.NotificationActivity
|
||||
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
@ -129,6 +130,8 @@ class HomeFragment : Fragment() {
|
||||
// Setup cart and notification buttons
|
||||
binding.searchContainer.btnCart.setOnClickListener {
|
||||
// Navigate to cart
|
||||
val intent = Intent(requireContext(), CartActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.searchContainer.btnNotification.setOnClickListener {
|
||||
|
@ -9,7 +9,7 @@ import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.PaymentInfoItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
@ -100,7 +100,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
if (cartResult is Result.Success) {
|
||||
// Find matching cart items
|
||||
val matchingItems = mutableListOf<CartItemsItem>()
|
||||
var storeData: DataItem? = null
|
||||
var storeData: DataItemCart? = null
|
||||
|
||||
for (store in cartResult.data) {
|
||||
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
|
||||
|
@ -28,6 +28,7 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
||||
import com.alya.ecommerce_serang.ui.cart.CartActivity
|
||||
import com.alya.ecommerce_serang.ui.chat.ChatActivity
|
||||
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
||||
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
||||
@ -205,9 +206,19 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val searchContainerView = binding.searchContainer
|
||||
searchContainerView.btnCart.setOnClickListener{
|
||||
navigateToCart()
|
||||
}
|
||||
|
||||
setupRecyclerViewOtherProducts()
|
||||
}
|
||||
|
||||
private fun navigateToCart() {
|
||||
val intent = Intent(this, CartActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun updateUI(product: Product){
|
||||
binding.tvProductName.text = product.productName
|
||||
binding.tvPrice.text = formatCurrency(product.price.toDouble())
|
||||
@ -296,6 +307,7 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
|
||||
private fun showAddToCartPopup(productId: Int) {
|
||||
showQuantityDialog(productId, false)
|
||||
|
||||
}
|
||||
|
||||
private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
|
||||
|
@ -5,6 +5,128 @@
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".data.api.response.customer.cart.CartActivity">
|
||||
tools:context=".ui.cart.CartActivity">
|
||||
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/header" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvCart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="#F5F5F5"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bottomCheckoutLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header"/>
|
||||
|
||||
<!-- Bottom Checkout Layout -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/bottomCheckoutLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSelectAll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Semua"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total: "
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/cbSelectAll"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp0"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/tvTotalLabel"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCheckout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_button_outline"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="Beli (0)"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- Empty State View (Shown when cart is empty) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/emptyStateLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bottomCheckoutLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:src="@drawable/outline_shopping_cart_24" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Keranjang Anda kosong"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:text="Silakan tambahkan produk ke keranjang"
|
||||
android:textColor="#757575"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnShopNow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/bg_button_outline"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="Belanja Sekarang"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
104
app/src/main/res/layout/item_cart_product.xml
Normal file
104
app/src/main/res/layout/item_cart_product.xml
Normal file
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/white"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbItem"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/ivProduct"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/ivProduct" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivProduct"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintStart_toEndOf="@+id/cbItem"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:text="Product Name"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/ivProduct"
|
||||
app:layout_constraintTop_toTopOf="@+id/ivProduct" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Rp0"
|
||||
android:textColor="#0077B6"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="@+id/tvProductName"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvProductName" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/quantityController"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/tvPrice"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvProductName">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnMinus"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:background="@drawable/bg_button_filled"
|
||||
android:src="@drawable/baseline_add_24"
|
||||
app:tint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQuantity"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@android:color/black"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnPlus"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnMinus"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPlus"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:background="@drawable/bg_button_filled"
|
||||
android:src="@drawable/baseline_add_24"
|
||||
app:tint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
41
app/src/main/res/layout/item_store_cart.xml
Normal file
41
app/src/main/res/layout/item_store_cart.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/white"
|
||||
android:padding="12dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivStore"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:src="@drawable/outline_store_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/cbStore"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStoreName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Store Name"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/ivStore"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Reference in New Issue
Block a user