mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
add
This commit is contained in:
@ -18,8 +18,8 @@
|
|||||||
<application
|
<application
|
||||||
android:name=".app.App"
|
android:name=".app.App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@ -30,9 +30,11 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.chat.ChatActivity"
|
android:name=".ui.cart.MainActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<!-- <provider -->
|
<activity
|
||||||
|
android:name=".ui.chat.ChatActivity"
|
||||||
|
android:exported="false" /> <!-- <provider -->
|
||||||
<!-- android:name="androidx.startup.InitializationProvider" -->
|
<!-- android:name="androidx.startup.InitializationProvider" -->
|
||||||
<!-- android:authorities="${applicationId}.androidx-startup" -->
|
<!-- android:authorities="${applicationId}.androidx-startup" -->
|
||||||
<!-- tools:node="remove" /> -->
|
<!-- tools:node="remove" /> -->
|
||||||
@ -55,7 +57,7 @@
|
|||||||
android:name=".ui.order.detail.PaymentActivity"
|
android:name=".ui.order.detail.PaymentActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".data.api.response.customer.cart.CartActivity"
|
android:name=".ui.cart.CartActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.order.address.EditAddressActivity"
|
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
|
||||||
|
)
|
@ -5,13 +5,13 @@ import com.google.gson.annotations.SerializedName
|
|||||||
data class ListCartResponse(
|
data class ListCartResponse(
|
||||||
|
|
||||||
@field:SerializedName("data")
|
@field:SerializedName("data")
|
||||||
val data: List<DataItem>,
|
val data: List<DataItemCart>,
|
||||||
|
|
||||||
@field:SerializedName("message")
|
@field:SerializedName("message")
|
||||||
val message: String
|
val message: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class DataItem(
|
data class DataItemCart(
|
||||||
|
|
||||||
@field:SerializedName("store_id")
|
@field:SerializedName("store_id")
|
||||||
val storeId: Int,
|
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.OrderRequestBuy
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
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.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.SearchRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
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.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.SendChatResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.UpdateChatResponse
|
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.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.ListCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
|
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.CourierCostResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
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.ListCityResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
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.OrderDetailResponse
|
||||||
@ -197,6 +200,11 @@ interface ApiService {
|
|||||||
@Body updateCart: UpdateCart
|
@Body updateCart: UpdateCart
|
||||||
): Response<UpdateCartResponse>
|
): Response<UpdateCartResponse>
|
||||||
|
|
||||||
|
@DELETE("cart/delete/{id}")
|
||||||
|
suspend fun deleteCart(
|
||||||
|
@Path("id") cartItemId : Int
|
||||||
|
):Response<DeleteCartResponse>
|
||||||
|
|
||||||
@POST("couriercost")
|
@POST("couriercost")
|
||||||
suspend fun countCourierCost(
|
suspend fun countCourierCost(
|
||||||
@Body courierCost : CourierCostRequest
|
@Body courierCost : CourierCostRequest
|
||||||
@ -232,6 +240,11 @@ interface ApiService {
|
|||||||
@Part complaintimg: MultipartBody.Part
|
@Part complaintimg: MultipartBody.Part
|
||||||
): Response<ComplaintResponse>
|
): Response<ComplaintResponse>
|
||||||
|
|
||||||
|
@POST("review")
|
||||||
|
suspend fun createReview(
|
||||||
|
@Body contentReview : ReviewProductItem
|
||||||
|
): Response<CreateReviewResponse>
|
||||||
|
|
||||||
@POST("search")
|
@POST("search")
|
||||||
suspend fun saveSearchQuery(
|
suspend fun saveSearchQuery(
|
||||||
@Body searchRequest: SearchRequest
|
@Body searchRequest: SearchRequest
|
||||||
|
@ -2,28 +2,30 @@ package com.alya.ecommerce_serang.data.repository
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
|
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
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.CourierCostRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
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.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.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.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.OrderDetailResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse
|
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.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.StoreProduct
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
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.AddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
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 com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
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 {
|
return try {
|
||||||
val response = apiService.getCart()
|
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?> {
|
suspend fun fetchStoreDetail(storeId: Int): Result<StoreProduct?> {
|
||||||
return try {
|
return try {
|
||||||
val response = apiService.getDetailStore(storeId)
|
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.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
|
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.notif.NotificationActivity
|
||||||
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
|
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
@ -129,6 +130,8 @@ class HomeFragment : Fragment() {
|
|||||||
// Setup cart and notification buttons
|
// Setup cart and notification buttons
|
||||||
binding.searchContainer.btnCart.setOnClickListener {
|
binding.searchContainer.btnCart.setOnClickListener {
|
||||||
// Navigate to cart
|
// Navigate to cart
|
||||||
|
val intent = Intent(requireContext(), CartActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchContainer.btnNotification.setOnClickListener {
|
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.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
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.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.product.PaymentInfoItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
@ -100,7 +100,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
if (cartResult is Result.Success) {
|
if (cartResult is Result.Success) {
|
||||||
// Find matching cart items
|
// Find matching cart items
|
||||||
val matchingItems = mutableListOf<CartItemsItem>()
|
val matchingItems = mutableListOf<CartItemsItem>()
|
||||||
var storeData: DataItem? = null
|
var storeData: DataItemCart? = null
|
||||||
|
|
||||||
for (store in cartResult.data) {
|
for (store in cartResult.data) {
|
||||||
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
|
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.ProductRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
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.chat.ChatActivity
|
||||||
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
||||||
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
||||||
@ -205,9 +206,19 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val searchContainerView = binding.searchContainer
|
||||||
|
searchContainerView.btnCart.setOnClickListener{
|
||||||
|
navigateToCart()
|
||||||
|
}
|
||||||
|
|
||||||
setupRecyclerViewOtherProducts()
|
setupRecyclerViewOtherProducts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToCart() {
|
||||||
|
val intent = Intent(this, CartActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateUI(product: Product){
|
private fun updateUI(product: Product){
|
||||||
binding.tvProductName.text = product.productName
|
binding.tvProductName.text = product.productName
|
||||||
binding.tvPrice.text = formatCurrency(product.price.toDouble())
|
binding.tvPrice.text = formatCurrency(product.price.toDouble())
|
||||||
@ -296,6 +307,7 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun showAddToCartPopup(productId: Int) {
|
private fun showAddToCartPopup(productId: Int) {
|
||||||
showQuantityDialog(productId, false)
|
showQuantityDialog(productId, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
|
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"/>
|
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
|
||||||
|
@ -5,6 +5,128 @@
|
|||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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>
|
</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