From eb1eb47ce044439518252a758b5eb8b62c97da08 Mon Sep 17 00:00:00 2001 From: ArtemEmelyanov Date: Sat, 25 Nov 2023 22:37:59 +0400 Subject: [PATCH] Feature: complete basket implementation --- .../SneakerRecyclerView/CardSneaker.kt | 2 +- .../Screens/MyOrderScreen/OrderCard.kt | 8 +++ .../Screens/OrderScreen/CardSneaker.kt | 49 ++++++++++++----- .../composeui/Screens/OrderScreen/SubTotal.kt | 11 ++-- .../android_programming/dao/BasketDao.kt | 16 ++++++ .../model/BasketSneakers.kt | 1 + .../android_programming/model/OrderSneaker.kt | 3 +- .../repository/BasketRepoImpl.kt | 6 +++ .../repository/BasketRepository.kt | 6 +++ .../vmodel/AppViewModelProvider.kt | 2 +- .../vmodel/BasketViewModel.kt | 52 ++++++++++++++++++- .../vmodel/OrderViewModel.kt | 30 ++++++++--- 12 files changed, 158 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/example/android_programming/composeui/Screens/HomeScreen/SneakerRecyclerView/CardSneaker.kt b/app/src/main/java/com/example/android_programming/composeui/Screens/HomeScreen/SneakerRecyclerView/CardSneaker.kt index 8943a69..d7fc986 100644 --- a/app/src/main/java/com/example/android_programming/composeui/Screens/HomeScreen/SneakerRecyclerView/CardSneaker.kt +++ b/app/src/main/java/com/example/android_programming/composeui/Screens/HomeScreen/SneakerRecyclerView/CardSneaker.kt @@ -96,7 +96,7 @@ fun CardSneaker(item: Sneaker, navController: NavHostController, basketViewModel if(GlobalUser.getInstance().getUser() == null){ navController.navigate("login") }else{ - basketViewModel.addToBasket(BasketSneakers(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!)) + basketViewModel.addToBasket(BasketSneakers(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!, 1)) } }, modifier = Modifier diff --git a/app/src/main/java/com/example/android_programming/composeui/Screens/MyOrderScreen/OrderCard.kt b/app/src/main/java/com/example/android_programming/composeui/Screens/MyOrderScreen/OrderCard.kt index 1a8d24f..32aeaaf 100644 --- a/app/src/main/java/com/example/android_programming/composeui/Screens/MyOrderScreen/OrderCard.kt +++ b/app/src/main/java/com/example/android_programming/composeui/Screens/MyOrderScreen/OrderCard.kt @@ -11,7 +11,14 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.IconButton 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.collectAsState 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.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.android_programming.R diff --git a/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/CardSneaker.kt b/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/CardSneaker.kt index 394034a..7bc971b 100644 --- a/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/CardSneaker.kt +++ b/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/CardSneaker.kt @@ -1,5 +1,6 @@ package com.example.android_programming.composeui.Screens.OrderScreen +import android.annotation.SuppressLint import androidx.compose.foundation.Image import androidx.compose.foundation.background 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.ButtonDefaults import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.icons.Icons 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.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.Modifier 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.OrderViewModel +@SuppressLint("UnrememberedMutableState") @Composable 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( modifier = Modifier .fillMaxWidth() @@ -63,18 +74,32 @@ fun CardSneakerLike(item: Sneaker, basketViewModel: BasketViewModel = viewModel( Text(text = "${item.price} USD", color = Color.Red, fontSize = 16.sp) } - Button( - colors = ButtonDefaults.buttonColors( - backgroundColor = colorResource(id = R.color.figma_blue), - contentColor = Color.White - ), - onClick = { - basketViewModel.deleteSneakerFromBasket(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!) - }, - modifier = Modifier - .padding(end = 16.dp) - ) { - Icon(imageVector = Icons.Default.Delete, contentDescription = "delete") + Column { + Button( + colors = ButtonDefaults.buttonColors( + backgroundColor = colorResource(id = R.color.figma_blue), + contentColor = Color.White + ), + onClick = { + basketViewModel.deleteSneakerFromBasket(GlobalUser.getInstance().getUser()?.userId!!, item.sneakerId!!) + }, + modifier = Modifier + .padding(end = 16.dp) + ) { + 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") + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/SubTotal.kt b/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/SubTotal.kt index 8e2d1b0..f86a7ca 100644 --- a/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/SubTotal.kt +++ b/app/src/main/java/com/example/android_programming/composeui/Screens/OrderScreen/SubTotal.kt @@ -9,16 +9,21 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.example.android_programming.GlobalUser import com.example.android_programming.R import com.example.android_programming.vmodel.OrderViewModel @Composable fun SubTotal(orderViewModel: OrderViewModel) { + val userId = GlobalUser.getInstance().getUser()?.userId!! + orderViewModel.updateSubTotal(userId) + val subTotal = orderViewModel.subTotal.value Column( modifier = Modifier .padding(16.dp) @@ -41,7 +46,7 @@ fun SubTotal(orderViewModel: OrderViewModel) { modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.End ){ - Text(text = "${orderViewModel.getSubTotal()} $", fontSize = 15.sp) + Text(text = "$subTotal $", fontSize = 15.sp) } } Row( @@ -60,7 +65,7 @@ fun SubTotal(orderViewModel: OrderViewModel) { modifier = Modifier.weight(1f), 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( @@ -79,7 +84,7 @@ fun SubTotal(orderViewModel: OrderViewModel) { modifier = Modifier.weight(1f), 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) } } } diff --git a/app/src/main/java/com/example/android_programming/dao/BasketDao.kt b/app/src/main/java/com/example/android_programming/dao/BasketDao.kt index 2c47b7f..94f36e6 100644 --- a/app/src/main/java/com/example/android_programming/dao/BasketDao.kt +++ b/app/src/main/java/com/example/android_programming/dao/BasketDao.kt @@ -28,4 +28,20 @@ interface BasketDao { @Query("DELETE FROM 'BasketSneakers' WHERE basketId = :basketId AND sneakerId = :sneakerId") 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? } \ No newline at end of file diff --git a/app/src/main/java/com/example/android_programming/model/BasketSneakers.kt b/app/src/main/java/com/example/android_programming/model/BasketSneakers.kt index e69706d..da4ccdb 100644 --- a/app/src/main/java/com/example/android_programming/model/BasketSneakers.kt +++ b/app/src/main/java/com/example/android_programming/model/BasketSneakers.kt @@ -7,4 +7,5 @@ import androidx.room.PrimaryKey class BasketSneakers ( val basketId: Int, val sneakerId: Int, + val quantity: Int ) \ No newline at end of file diff --git a/app/src/main/java/com/example/android_programming/model/OrderSneaker.kt b/app/src/main/java/com/example/android_programming/model/OrderSneaker.kt index 2fc6243..5602ab3 100644 --- a/app/src/main/java/com/example/android_programming/model/OrderSneaker.kt +++ b/app/src/main/java/com/example/android_programming/model/OrderSneaker.kt @@ -5,5 +5,6 @@ import androidx.room.Entity @Entity(primaryKeys = ["orderId", "sneakerId"]) data class OrderSneaker( val orderId: Int, - val sneakerId: Int + val sneakerId: Int, + val quantity: Int ) \ No newline at end of file diff --git a/app/src/main/java/com/example/android_programming/repository/BasketRepoImpl.kt b/app/src/main/java/com/example/android_programming/repository/BasketRepoImpl.kt index c37ce54..761984d 100644 --- a/app/src/main/java/com/example/android_programming/repository/BasketRepoImpl.kt +++ b/app/src/main/java/com/example/android_programming/repository/BasketRepoImpl.kt @@ -9,8 +9,14 @@ import kotlinx.coroutines.flow.Flow class BasketRepoImpl(private val basketDao: BasketDao): BasketRepository { 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 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 fun getBasketWithSneakers(id: Int): Flow = basketDao.getBasketWithSneakers(id) override fun getAllBasket(): Flow> = basketDao.getAllBasket() 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) } \ No newline at end of file diff --git a/app/src/main/java/com/example/android_programming/repository/BasketRepository.kt b/app/src/main/java/com/example/android_programming/repository/BasketRepository.kt index 6c80fee..692dda3 100644 --- a/app/src/main/java/com/example/android_programming/repository/BasketRepository.kt +++ b/app/src/main/java/com/example/android_programming/repository/BasketRepository.kt @@ -15,4 +15,10 @@ interface BasketRepository { suspend fun delete(basket: Basket) suspend fun createBasket(basket: Basket):Long 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? } \ No newline at end of file diff --git a/app/src/main/java/com/example/android_programming/vmodel/AppViewModelProvider.kt b/app/src/main/java/com/example/android_programming/vmodel/AppViewModelProvider.kt index 15aef32..186ab7c 100644 --- a/app/src/main/java/com/example/android_programming/vmodel/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/android_programming/vmodel/AppViewModelProvider.kt @@ -15,7 +15,7 @@ object AppViewModelProvider { UserViewModel(app().container.userRepo) } initializer { - OrderViewModel(app().container.orderRepo) + OrderViewModel(app().container.orderRepo, app().container.basketRepo) } initializer { BasketViewModel(app().container.basketRepo) diff --git a/app/src/main/java/com/example/android_programming/vmodel/BasketViewModel.kt b/app/src/main/java/com/example/android_programming/vmodel/BasketViewModel.kt index 53e7ddf..181f165 100644 --- a/app/src/main/java/com/example/android_programming/vmodel/BasketViewModel.kt +++ b/app/src/main/java/com/example/android_programming/vmodel/BasketViewModel.kt @@ -1,6 +1,8 @@ package com.example.android_programming.vmodel +import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope 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.repository.BasketRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch class BasketViewModel(private val basketRepository: BasketRepository): ViewModel() { + private val _quantityStateMap = mutableMapOf>() + + fun getQuantityState(basketId: Int, sneakerId: Int): StateFlow { + 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 { - 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 { @@ -24,4 +52,24 @@ class BasketViewModel(private val basketRepository: BasketRepository): ViewModel fun deleteSneakerFromBasket(basketId: Int, sneakerId: Int) = viewModelScope.launch { basketRepository.removeSneakerFromBasket(basketId, sneakerId) } -} \ No newline at end of file + + 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) + } + } + } +} diff --git a/app/src/main/java/com/example/android_programming/vmodel/OrderViewModel.kt b/app/src/main/java/com/example/android_programming/vmodel/OrderViewModel.kt index 4b4675d..e3010b8 100644 --- a/app/src/main/java/com/example/android_programming/vmodel/OrderViewModel.kt +++ b/app/src/main/java/com/example/android_programming/vmodel/OrderViewModel.kt @@ -1,5 +1,6 @@ package com.example.android_programming.vmodel +import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -23,12 +24,14 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch 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("") val street = mutableStateOf("") val house = mutableStateOf("") private val _selectedItems = MutableLiveData>() + private val _subTotal = mutableStateOf(0.0) + val subTotal: State get() = _subTotal val selectedItems: LiveData> get() = _selectedItems fun updateSelectedItems(items: List) { @@ -48,29 +51,40 @@ class OrderViewModel(private val orderRepository: OrderRepository) : ViewModel() } fun createOrder() = viewModelScope.launch { + val userId = GlobalUser.getInstance().getUser()?.userId!! val order = Order( date = Date().time, city = city.value, street = street.value, house = house.value, - subtotal = getSubTotal(), - taxes = "%.2f".format(getSubTotal() * 0.05).toDouble(), - total = "%.2f".format(getSubTotal() * 0.05 + getSubTotal()).toDouble(), + subtotal = getSubTotal(userId), + taxes = "%.2f".format(getSubTotal(userId) * 0.05).toDouble(), + total = "%.2f".format(getSubTotal(userId) * 0.05 + getSubTotal(userId)).toDouble(), creatorUserId = GlobalUser.getInstance().getUser()?.userId!! ) val orderId = orderRepository.createOrder(order) for (sneaker in selectedItems.value.orEmpty()) { - val orderSneaker = OrderSneaker( orderId.toInt(), sneaker.sneakerId!!) - orderRepository.insertOrderSneaker(orderSneaker) + val userId = GlobalUser.getInstance().getUser()?.userId!! + val orderSneaker = basketRepository.getQuantity(userId, sneaker.sneakerId!!) + ?.let { OrderSneaker( orderId.toInt(), sneaker.sneakerId!!, it) } + if (orderSneaker != null) { + orderRepository.insertOrderSneaker(orderSneaker) + } } city.value = "" street.value = "" house.value = "" } - fun getSubTotal(): Double { - return 0.0 + fun updateSubTotal(userId: Int) { + viewModelScope.launch { + _subTotal.value = getSubTotal(userId) + } + } + + suspend fun getSubTotal(userId: Int): Double { + return basketRepository.getTotalPriceForUser(userId) ?: 0.0 } } \ No newline at end of file