Feature: complete basket implementation

This commit is contained in:
ArtemEmelyanov 2023-11-25 22:37:59 +04:00
parent 52d4438a41
commit eb1eb47ce0
12 changed files with 158 additions and 28 deletions

View File

@ -96,7 +96,7 @@ fun CardSneaker(item: Sneaker, navController: NavHostController, basketViewModel
if(GlobalUser.getInstance().getUser() == null){ if(GlobalUser.getInstance().getUser() == null){
navController.navigate("login") navController.navigate("login")
}else{ }else{
basketViewModel.addToBasket(BasketSneakers(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!)) basketViewModel.addToBasket(BasketSneakers(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!, 1))
} }
}, },
modifier = Modifier modifier = Modifier

View File

@ -11,7 +11,14 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -21,6 +28,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.android_programming.R import com.example.android_programming.R

View File

@ -1,5 +1,6 @@
package com.example.android_programming.composeui.Screens.OrderScreen package com.example.android_programming.composeui.Screens.OrderScreen
import android.annotation.SuppressLint
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -12,10 +13,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -33,8 +41,11 @@ import com.example.android_programming.vmodel.AppViewModelProvider
import com.example.android_programming.vmodel.BasketViewModel import com.example.android_programming.vmodel.BasketViewModel
import com.example.android_programming.vmodel.OrderViewModel import com.example.android_programming.vmodel.OrderViewModel
@SuppressLint("UnrememberedMutableState")
@Composable @Composable
fun CardSneakerLike(item: Sneaker, basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory)) { fun CardSneakerLike(item: Sneaker, basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val userId = GlobalUser.getInstance().getUser()?.userId!!
val quantityState by basketViewModel.getQuantityState(userId, item.sneakerId!!).collectAsState()
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -63,18 +74,32 @@ fun CardSneakerLike(item: Sneaker, basketViewModel: BasketViewModel = viewModel(
Text(text = "${item.price} USD", color = Color.Red, fontSize = 16.sp) Text(text = "${item.price} USD", color = Color.Red, fontSize = 16.sp)
} }
Button( Column {
colors = ButtonDefaults.buttonColors( Button(
backgroundColor = colorResource(id = R.color.figma_blue), colors = ButtonDefaults.buttonColors(
contentColor = Color.White backgroundColor = colorResource(id = R.color.figma_blue),
), contentColor = Color.White
onClick = { ),
basketViewModel.deleteSneakerFromBasket(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!) onClick = {
}, basketViewModel.deleteSneakerFromBasket(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!)
modifier = Modifier },
.padding(end = 16.dp) modifier = Modifier
) { .padding(end = 16.dp)
Icon(imageVector = Icons.Default.Delete, contentDescription = "delete") ) {
Icon(imageVector = Icons.Default.Delete, contentDescription = "delete")
}
}
Column {
Row {
IconButton(onClick = { basketViewModel.decrementQuantity(userId ,item.sneakerId!!) }) {
Icon(Icons.Default.KeyboardArrowLeft, contentDescription = "Decrease Quantity")
}
Text("$quantityState")
IconButton(onClick = { basketViewModel.incrementQuantity(userId, item.sneakerId!!) }) {
Icon(Icons.Default.KeyboardArrowRight, contentDescription = "Increase Quantity")
}
}
} }
} }
} }

View File

@ -9,16 +9,21 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.android_programming.GlobalUser
import com.example.android_programming.R import com.example.android_programming.R
import com.example.android_programming.vmodel.OrderViewModel import com.example.android_programming.vmodel.OrderViewModel
@Composable @Composable
fun SubTotal(orderViewModel: OrderViewModel) { fun SubTotal(orderViewModel: OrderViewModel) {
val userId = GlobalUser.getInstance().getUser()?.userId!!
orderViewModel.updateSubTotal(userId)
val subTotal = orderViewModel.subTotal.value
Column( Column(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)
@ -41,7 +46,7 @@ fun SubTotal(orderViewModel: OrderViewModel) {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
){ ){
Text(text = "${orderViewModel.getSubTotal()} $", fontSize = 15.sp) Text(text = "$subTotal $", fontSize = 15.sp)
} }
} }
Row( Row(
@ -60,7 +65,7 @@ fun SubTotal(orderViewModel: OrderViewModel) {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
){ ){
Text(text = "${"%.2f".format(orderViewModel.getSubTotal() * 0.05)} $", fontSize = 15.sp) Text(text = "${"%.2f".format(subTotal * 0.05)} $", fontSize = 15.sp)
} }
} }
Row( Row(
@ -79,7 +84,7 @@ fun SubTotal(orderViewModel: OrderViewModel) {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
){ ){
Text(text = "${"%.2f".format(orderViewModel.getSubTotal() + orderViewModel.getSubTotal() * 0.05)} $", fontSize = 15.sp) Text(text = "${"%.2f".format(subTotal + subTotal * 0.05)} $", fontSize = 15.sp)
} }
} }
} }

View File

@ -28,4 +28,20 @@ interface BasketDao {
@Query("DELETE FROM 'BasketSneakers' WHERE basketId = :basketId AND sneakerId = :sneakerId") @Query("DELETE FROM 'BasketSneakers' WHERE basketId = :basketId AND sneakerId = :sneakerId")
suspend fun removeSneakerFromBasket(basketId: Int, sneakerId: Int) suspend fun removeSneakerFromBasket(basketId: Int, sneakerId: Int)
@Query("UPDATE 'BasketSneakers' SET quantity = :quantity WHERE basketId = :basketId AND sneakerId = :sneakerId")
suspend fun updateSneakerQuantity(basketId: Int, sneakerId: Int, quantity: Int)
@Query("UPDATE 'BasketSneakers' SET quantity = quantity + 1 WHERE basketId = :basketId AND sneakerId = :sneakerId")
suspend fun incrementSneakerQuantity(basketId: Int, sneakerId: Int)
@Query("UPDATE 'BasketSneakers' SET quantity = quantity - 1 WHERE basketId = :basketId AND sneakerId = :sneakerId")
suspend fun decrementSneakerQuantity(basketId: Int, sneakerId: Int)
@Query("SELECT quantity FROM 'BasketSneakers' WHERE basketId = :basketId AND sneakerId = :sneakerId")
suspend fun getQuantity(basketId: Int, sneakerId: Int): Int?
@Query("SELECT * FROM 'BasketSneakers' WHERE basketId = :basketId AND sneakerId = :sneakerId")
suspend fun getSneaker(basketId: Int, sneakerId: Int): BasketSneakers?
@Query("SELECT SUM(price * quantity) FROM 'BasketSneakers' bs JOIN 'Sneaker' s ON bs.sneakerId = s.sneakerId WHERE bs.basketId = :userId")
suspend fun getTotalPriceForUser(userId: Int): Double?
} }

View File

@ -7,4 +7,5 @@ import androidx.room.PrimaryKey
class BasketSneakers ( class BasketSneakers (
val basketId: Int, val basketId: Int,
val sneakerId: Int, val sneakerId: Int,
val quantity: Int
) )

View File

@ -5,5 +5,6 @@ import androidx.room.Entity
@Entity(primaryKeys = ["orderId", "sneakerId"]) @Entity(primaryKeys = ["orderId", "sneakerId"])
data class OrderSneaker( data class OrderSneaker(
val orderId: Int, val orderId: Int,
val sneakerId: Int val sneakerId: Int,
val quantity: Int
) )

View File

@ -9,8 +9,14 @@ import kotlinx.coroutines.flow.Flow
class BasketRepoImpl(private val basketDao: BasketDao): BasketRepository { class BasketRepoImpl(private val basketDao: BasketDao): BasketRepository {
override suspend fun createBasket(basket: Basket): Long = basketDao.createBasket(basket) override suspend fun createBasket(basket: Basket): Long = basketDao.createBasket(basket)
override suspend fun removeSneakerFromBasket(basketId: Int, sneakerId: Int) = basketDao.removeSneakerFromBasket(basketId, sneakerId) override suspend fun removeSneakerFromBasket(basketId: Int, sneakerId: Int) = basketDao.removeSneakerFromBasket(basketId, sneakerId)
override suspend fun updateSneakerQuantity(basketId: Int, sneakerId: Int, quantity: Int) = basketDao.updateSneakerQuantity(basketId, sneakerId, quantity)
override suspend fun incrementSneakerQuantity(basketId: Int, sneakerId: Int) = basketDao.incrementSneakerQuantity(basketId, sneakerId)
override suspend fun decrementSneakerQuantity(basketId: Int, sneakerId: Int) = basketDao.decrementSneakerQuantity(basketId, sneakerId)
override suspend fun insertBasketSneaker(basketSneaker: BasketSneakers) = basketDao.insertBasketSneaker(basketSneaker) override suspend fun insertBasketSneaker(basketSneaker: BasketSneakers) = basketDao.insertBasketSneaker(basketSneaker)
override fun getBasketWithSneakers(id: Int): Flow<BasketWithSneakers> = basketDao.getBasketWithSneakers(id) override fun getBasketWithSneakers(id: Int): Flow<BasketWithSneakers> = basketDao.getBasketWithSneakers(id)
override fun getAllBasket(): Flow<List<Basket>> = basketDao.getAllBasket() override fun getAllBasket(): Flow<List<Basket>> = basketDao.getAllBasket()
override suspend fun delete(basket: Basket) = basketDao.delete(basket) override suspend fun delete(basket: Basket) = basketDao.delete(basket)
override suspend fun getQuantity(basketId: Int, sneakerId: Int): Int? = basketDao.getQuantity(basketId, sneakerId)
override suspend fun getSneaker(basketId: Int, sneakerId: Int): BasketSneakers? = basketDao.getSneaker(basketId, sneakerId)
override suspend fun getTotalPriceForUser(userId: Int): Double? = basketDao.getTotalPriceForUser(userId)
} }

View File

@ -15,4 +15,10 @@ interface BasketRepository {
suspend fun delete(basket: Basket) suspend fun delete(basket: Basket)
suspend fun createBasket(basket: Basket):Long suspend fun createBasket(basket: Basket):Long
suspend fun removeSneakerFromBasket(basketId: Int, sneakerId: Int) suspend fun removeSneakerFromBasket(basketId: Int, sneakerId: Int)
suspend fun updateSneakerQuantity(basketId: Int, sneakerId: Int, quantity: Int)
suspend fun incrementSneakerQuantity(basketId: Int, sneakerId: Int)
suspend fun decrementSneakerQuantity(basketId: Int, sneakerId: Int)
suspend fun getQuantity(basketId: Int, sneakerId: Int): Int?
suspend fun getSneaker(basketId: Int, sneakerId: Int): BasketSneakers?
suspend fun getTotalPriceForUser(userId: Int): Double?
} }

View File

@ -15,7 +15,7 @@ object AppViewModelProvider {
UserViewModel(app().container.userRepo) UserViewModel(app().container.userRepo)
} }
initializer { initializer {
OrderViewModel(app().container.orderRepo) OrderViewModel(app().container.orderRepo, app().container.basketRepo)
} }
initializer { initializer {
BasketViewModel(app().container.basketRepo) BasketViewModel(app().container.basketRepo)

View File

@ -1,6 +1,8 @@
package com.example.android_programming.vmodel package com.example.android_programming.vmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.android_programming.model.Basket import com.example.android_programming.model.Basket
@ -9,12 +11,38 @@ import com.example.android_programming.model.BasketWithSneakers
import com.example.android_programming.model.Sneaker import com.example.android_programming.model.Sneaker
import com.example.android_programming.repository.BasketRepository import com.example.android_programming.repository.BasketRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class BasketViewModel(private val basketRepository: BasketRepository): ViewModel() { class BasketViewModel(private val basketRepository: BasketRepository): ViewModel() {
private val _quantityStateMap = mutableMapOf<Int, MutableStateFlow<Int>>()
fun getQuantityState(basketId: Int, sneakerId: Int): StateFlow<Int> {
val quantityStateFlow = _quantityStateMap.getOrPut(sneakerId) {
MutableStateFlow(0)
}
viewModelScope.launch {
val quantityFromDb = basketRepository.getQuantity(basketId, sneakerId)
quantityFromDb?.let { quantityStateFlow.value = it }
}
return quantityStateFlow
}
suspend fun isSneakerInBasket(basketId: Int, sneakerId: Int): Boolean {
return basketRepository.getSneaker(basketId, sneakerId) != null
}
fun addToBasket(basketSneakers: BasketSneakers) = viewModelScope.launch { fun addToBasket(basketSneakers: BasketSneakers) = viewModelScope.launch {
basketRepository.insertBasketSneaker(basketSneakers) val isSneakerInBasket = isSneakerInBasket(basketSneakers.basketId, basketSneakers.sneakerId)
if (isSneakerInBasket) {
incrementQuantity(basketSneakers.basketId, basketSneakers.sneakerId)
} else {
basketRepository.insertBasketSneaker(basketSneakers)
}
} }
fun getBasketSneakers(id: Int): Flow<BasketWithSneakers> { fun getBasketSneakers(id: Int): Flow<BasketWithSneakers> {
@ -24,4 +52,24 @@ class BasketViewModel(private val basketRepository: BasketRepository): ViewModel
fun deleteSneakerFromBasket(basketId: Int, sneakerId: Int) = viewModelScope.launch { fun deleteSneakerFromBasket(basketId: Int, sneakerId: Int) = viewModelScope.launch {
basketRepository.removeSneakerFromBasket(basketId, sneakerId) basketRepository.removeSneakerFromBasket(basketId, sneakerId)
} }
fun incrementQuantity(basketId: Int, sneakerId: Int) {
val currentQuantity = _quantityStateMap[sneakerId]?.value ?: 1
_quantityStateMap[sneakerId]?.value = currentQuantity + 1
viewModelScope.launch {
basketRepository.incrementSneakerQuantity(basketId, sneakerId)
}
}
fun decrementQuantity(basketId: Int, sneakerId: Int) {
val currentQuantity = _quantityStateMap[sneakerId]?.value ?: 1
if (currentQuantity > 1) {
_quantityStateMap[sneakerId]?.value = currentQuantity - 1
viewModelScope.launch {
basketRepository.decrementSneakerQuantity(basketId, sneakerId)
}
}
}
} }

View File

@ -1,5 +1,6 @@
package com.example.android_programming.vmodel package com.example.android_programming.vmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -23,12 +24,14 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Date import java.util.Date
class OrderViewModel(private val orderRepository: OrderRepository) : ViewModel() { class OrderViewModel(private val orderRepository: OrderRepository, private val basketRepository: BasketRepository) : ViewModel() {
var city = mutableStateOf("") var city = mutableStateOf("")
val street = mutableStateOf("") val street = mutableStateOf("")
val house = mutableStateOf("") val house = mutableStateOf("")
private val _selectedItems = MutableLiveData<List<Sneaker>>() private val _selectedItems = MutableLiveData<List<Sneaker>>()
private val _subTotal = mutableStateOf(0.0)
val subTotal: State<Double> get() = _subTotal
val selectedItems: LiveData<List<Sneaker>> get() = _selectedItems val selectedItems: LiveData<List<Sneaker>> get() = _selectedItems
fun updateSelectedItems(items: List<Sneaker>) { fun updateSelectedItems(items: List<Sneaker>) {
@ -48,29 +51,40 @@ class OrderViewModel(private val orderRepository: OrderRepository) : ViewModel()
} }
fun createOrder() = viewModelScope.launch { fun createOrder() = viewModelScope.launch {
val userId = GlobalUser.getInstance().getUser()?.userId!!
val order = Order( val order = Order(
date = Date().time, date = Date().time,
city = city.value, city = city.value,
street = street.value, street = street.value,
house = house.value, house = house.value,
subtotal = getSubTotal(), subtotal = getSubTotal(userId),
taxes = "%.2f".format(getSubTotal() * 0.05).toDouble(), taxes = "%.2f".format(getSubTotal(userId) * 0.05).toDouble(),
total = "%.2f".format(getSubTotal() * 0.05 + getSubTotal()).toDouble(), total = "%.2f".format(getSubTotal(userId) * 0.05 + getSubTotal(userId)).toDouble(),
creatorUserId = GlobalUser.getInstance().getUser()?.userId!! creatorUserId = GlobalUser.getInstance().getUser()?.userId!!
) )
val orderId = orderRepository.createOrder(order) val orderId = orderRepository.createOrder(order)
for (sneaker in selectedItems.value.orEmpty()) { for (sneaker in selectedItems.value.orEmpty()) {
val orderSneaker = OrderSneaker( orderId.toInt(), sneaker.sneakerId!!) val userId = GlobalUser.getInstance().getUser()?.userId!!
orderRepository.insertOrderSneaker(orderSneaker) val orderSneaker = basketRepository.getQuantity(userId, sneaker.sneakerId!!)
?.let { OrderSneaker( orderId.toInt(), sneaker.sneakerId!!, it) }
if (orderSneaker != null) {
orderRepository.insertOrderSneaker(orderSneaker)
}
} }
city.value = "" city.value = ""
street.value = "" street.value = ""
house.value = "" house.value = ""
} }
fun getSubTotal(): Double { fun updateSubTotal(userId: Int) {
return 0.0 viewModelScope.launch {
_subTotal.value = getSubTotal(userId)
}
}
suspend fun getSubTotal(userId: Int): Double {
return basketRepository.getTotalPriceForUser(userId) ?: 0.0
} }
} }