KorzinaWindow;orderMethods

This commit is contained in:
Your Name 2025-06-05 23:26:28 +03:00
parent 86c9b9fd23
commit e3c4a408b1
14 changed files with 530 additions and 26 deletions

View File

@ -17,7 +17,12 @@ import com.example.testktor.screen.main.MainSneakersScreen
import com.example.testktor.screen.auth.RegScreen
import com.example.testktor.screen.auth.ResetPasswordScreen
import com.example.testktor.screen.main.CategorySneakersScreen
import com.example.testktor.screen.main.FavouriteSneakersContent
import com.example.testktor.screen.main.FavouriteSneakersScreen
import com.example.testktor.screen.main.KorzinaSneakersContent
import com.example.testktor.screen.main.KorzinaSneakersScreen
import com.example.testktor.screen.main.PopularSneakersScreen
import com.example.testktor.screen.main.SuccessOrderSneakersScreen
//import com.example.testktor.auth.AuthScreen
//import com.example.testktor.auth.RegistrationScreen
import com.example.testktor.ui.theme.TestKtorTheme
@ -65,6 +70,15 @@ class MainActivity : ComponentActivity() {
composable("categorySneakers_screen") {
CategorySneakersScreen(navController, viewModel)
}
composable("favouriteSneakers_screen") {
FavouriteSneakersScreen(navController, viewModel)
}
composable("korzinaSneakers_screen") {
KorzinaSneakersScreen(navController, viewModel)
}
composable("successOrderSneakers_screen") {
SuccessOrderSneakersScreen(navController, viewModel)
}
}
}
}

View File

@ -26,7 +26,8 @@ data class Sneaker(
@Serializable
data class SneakerShop(
val sneaker: Sneaker,
var likeOrNot: Boolean
var likeOrNot: Boolean,
var orderOrNot: Boolean
)
@Serializable

View File

@ -1,5 +1,6 @@
package com.example.testktor.ViewModel
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
@ -7,8 +8,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.testktor.BuySneakerRequest
import com.example.testktor.BuySneakersRequest
import com.example.testktor.Category
import com.example.testktor.SneakerCategoryResponse
import com.example.testktor.SneakerShop
import com.example.testktor.getSneakers
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
@ -21,6 +25,9 @@ class MainViewModel : ViewModel() {
var selectedCategory by mutableStateOf<Category?>(null)
var likedSneakers = mutableStateMapOf<Int, Boolean>()
var orderedSneakers = mutableStateMapOf<Int, Boolean>()
var sneakerQuantities = mutableStateMapOf<Int, Int>()
private val client = HttpClient(Android)
@ -38,6 +45,67 @@ class MainViewModel : ViewModel() {
return likedSneakers[sneakerId] ?: false
}
fun toggleOrder(sneakerId: Int) {
orderedSneakers[sneakerId] = !(orderedSneakers[sneakerId] ?: false)
}
fun isOrdered(sneakerId: Int): Boolean {
return orderedSneakers[sneakerId] ?: false
}
fun setSneakerQuantity(sneakerId: Int, quantity: Int) {
sneakerQuantities[sneakerId] = quantity
}
fun getSneakerQuantity(sneakerId: Int): Int {
return sneakerQuantities[sneakerId] ?: 1
}
fun getLikedSneakersByCategory(): List<SneakerShop> {
return sneakersCategories.sneakers.filter {
likedSneakers[it.id] == true && (selectedCategory == null || it.categoryId == selectedCategory?.id)
}.map {
SneakerShop(it, likeOrNot = true, orderOrNot = isOrdered(it.id))
}
}
fun getOrderedSneakersByCategory(): List<SneakerShop> {
return sneakersCategories.sneakers.filter {
orderedSneakers[it.id] == true && (selectedCategory == null || it.categoryId == selectedCategory?.id)
}.map {
SneakerShop(it, likeOrNot = isLiked(it.id), orderOrNot = true)
}
}
fun getLikedSneakersTotalPrice(): Double {
return getOrderedSneakersByCategory().sumOf {
(it.sneaker.cost * (sneakerQuantities[it.sneaker.id] ?: 1))
}
}
fun removeSneakerFromCart(id: Int) {
orderedSneakers[id] = false
sneakerQuantities.remove(id)
}
fun generateBuySneakersRequest(): BuySneakersRequest {
val orderedSneakersList = getOrderedSneakersByCategory()
Log.d("generateBuySneakersRequest", "Кроссовки в заказе: ${orderedSneakersList.size}")
val requestList = orderedSneakersList.map {
Log.d("generateBuySneakersRequest", "id=${it.sneaker.id}, count=${getSneakerQuantity(it.sneaker.id)}")
BuySneakerRequest(
sneakerId = it.sneaker.id,
count = getSneakerQuantity(it.sneaker.id)
)
}
return BuySneakersRequest(sneakers = requestList)
}
fun clearCart() {
orderedSneakers.clear()
sneakerQuantities.clear()
}
fun refreshData() {
viewModelScope.launch {
sneakersCategories = getSneakers(client) ?: SneakerCategoryResponse(emptyList(), emptyList())

View File

@ -146,7 +146,7 @@ suspend fun buySneakers(client: HttpClient, order: BuySneakersRequest): Boolean
headers {
append(HttpHeaders.ContentType, "application/json")
}
setBody(order)
setBody(Json.encodeToString(order))
}
val responseBody = response.bodyAsText()

View File

@ -62,11 +62,26 @@ fun SneakerCategorySelection(sneakers: List<SneakerShop>, viewModel: MainViewMod
SneakerButton(
sneakerShop = sneakerShop,
isLiked = viewModel.isLiked(sneakerShop.sneaker.id),
onClick = { /* TODO: Действие кнопки */ },
isOrdered = viewModel.isOrdered(sneakerShop.sneaker.id),
onClick = { /* переход */ },
onFavoriteClick = {
viewModel.toggleLike(sneakerShop.sneaker.id)
val likedCount = viewModel.likedSneakers.values.count { it }
Log.d("SneakerLike", "Лайкнуто кроссовок: $likedCount")
},
onOrderClick = {
val sneakerId = sneakerShop.sneaker.id
if (!viewModel.isOrdered(sneakerId)) {
viewModel.toggleOrder(sneakerId)
viewModel.setSneakerQuantity(sneakerId, 1)
} else {
viewModel.toggleOrder(sneakerId)
viewModel.setSneakerQuantity(sneakerId, 0)
}
val orderCount = viewModel.orderedSneakers.values.count { it }
Log.d("SneakerOrder", "Добавлено в корзину кроссовок: $orderCount")
}
)
}

View File

@ -0,0 +1,2 @@
package com.example.testktor.method.main

View File

@ -0,0 +1,264 @@
package com.example.testktor.method.main
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults.buttonColors
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.example.testktor.SneakerShop
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.buySneakers
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import kotlinx.coroutines.launch
import androidx.compose.ui.platform.LocalContext
import androidx.compose.foundation.Canvas
@Composable
fun BottomKorzinaNavigationBar(
navController: NavController,
sneakers: List<SneakerShop>,
viewModel: MainViewModel
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val client = remember { HttpClient(Android) }
val totalPrice by remember(sneakers) {
derivedStateOf {
sneakers.sumOf {
it.sneaker.cost * viewModel.getSneakerQuantity(it.sneaker.id)
}
}
}
val totalPriceWithDelivery by remember(totalPrice) {
derivedStateOf { totalPrice + 60 }
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.LightGray),
contentAlignment = Alignment.BottomCenter
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = "Сумма", fontSize = 18.sp)
Text(text = "$totalPrice рубликов", fontSize = 18.sp)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = "Доставка", fontSize = 18.sp)
Text(text = "60 рубликов", fontSize = 18.sp)
}
Canvas(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.height(1.dp)
) {
val dashWidth = 10f
val gapWidth = 10f
var currentX = 0f
while (currentX < size.width) {
drawLine(
color = Color.Gray,
start = Offset(currentX, 0f),
end = Offset(currentX + dashWidth, 0f),
strokeWidth = 2f
)
currentX += dashWidth + gapWidth
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = "Итого", fontSize = 18.sp)
Text(text = "$totalPriceWithDelivery рубликов", fontSize = 18.sp)
}
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
.height(50.dp),
shape = RoundedCornerShape(10.dp),
colors = buttonColors(
containerColor = Color(0xFF48B2E7),
contentColor = Color.White
),
onClick = {
coroutineScope.launch {
val order = viewModel.generateBuySneakersRequest()
Log.d("BottomKorzinaNavigationBar", "Order: $order")
val success = buySneakers(client, order)
if (success) {
viewModel.clearCart()
Toast.makeText(context, "Покупка успешно оформлена", Toast.LENGTH_SHORT).show()
navController.navigate("successOrderSneakers_screen")
} else {
Toast.makeText(context, "Ошибка при покупке", Toast.LENGTH_SHORT).show()
}
}
}
) {
Text("Оформить заказ", fontSize = 16.sp)
}
}
}
}
@Composable
fun SneakerKorzinaButton(
sneakerShop: SneakerShop,
quantity: Int,
onClick: () -> Unit,
onIncrease: () -> Unit,
onDecrease: () -> Unit,
onRemove: () -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Column(
modifier = Modifier
.background(Color(0xFF48B2E7), RoundedCornerShape(8.dp))
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(onClick = onIncrease) {
Text("+", color = Color.White, fontSize = 18.sp)
}
Text("$quantity", color = Color.White, fontSize = 16.sp)
IconButton(onClick = onDecrease) {
Text("-", color = Color.White, fontSize = 18.sp)
}
}
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = onClick,
modifier = Modifier
.width(140.dp)
.height(180.dp),
colors = buttonColors(containerColor = Color.White),
shape = RoundedCornerShape(12.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AsyncImage(
model = sneakerShop.sneaker.photo,
contentDescription = "Sneaker Image",
modifier = Modifier
.width(200.dp)
.height(90.dp)
)
Text(text = sneakerShop.sneaker.name, fontSize = 20.sp, color = Color.DarkGray)
Text(text = "${sneakerShop.sneaker.cost}", fontSize = 15.sp, color = Color.Black)
}
}
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.background(Color.Red, RoundedCornerShape(8.dp))
.size(40.dp),
contentAlignment = Alignment.Center
) {
IconButton(onClick = onRemove) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить",
tint = Color.White
)
}
}
}
}
@Composable
fun SneakerKorzinaSelection(sneakers: List<SneakerShop>, viewModel: MainViewModel) {
Column(modifier = Modifier.fillMaxWidth()) {
sneakers.chunked(1).forEach { sneakerPair ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
sneakerPair.forEach { sneakerShop ->
val sneakerId = sneakerShop.sneaker.id
val quantity = viewModel.getSneakerQuantity(sneakerId)
SneakerKorzinaButton(
sneakerShop = sneakerShop,
quantity = quantity,
onClick = { /* переход */ },
onIncrease = {
viewModel.setSneakerQuantity(sneakerId, quantity + 1)
},
onDecrease = {
if (quantity > 1) {
viewModel.setSneakerQuantity(sneakerId, quantity - 1)
}
},
onRemove = { viewModel.removeSneakerFromCart(sneakerId) }
)
}
}
}
}
}

View File

@ -38,10 +38,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -205,9 +201,8 @@ fun SalesBar() {
}
}
@Preview(showBackground = true)
@Composable
fun BottomNavigationBar() {
fun BottomNavigationBar(navController: NavController) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
@ -236,7 +231,7 @@ fun BottomNavigationBar() {
)
}
Spacer(modifier = Modifier.width(20.dp))
IconButton(onClick = { /* TODO: действие кнопки */ }) {
IconButton(onClick = { navController.navigate("favouriteSneakers_screen")}) {
Icon(
imageVector = Icons.Default.FavoriteBorder,
contentDescription = "favourite",
@ -245,7 +240,7 @@ fun BottomNavigationBar() {
}
Spacer(modifier = Modifier.width(20.dp))
IconButton(
onClick = { /* TODO: действие кнопки */ },
onClick = { navController.navigate("korzinaSneakers_screen") },
modifier = Modifier.offset(y = (-20).dp).size(80.dp)
) {
Image(
@ -321,12 +316,26 @@ fun SneakerSelection(sneakers: List<SneakerShop>, viewModel: MainViewModel) {
SneakerButton(
sneakerShop = sneakerShop,
isLiked = viewModel.isLiked(sneakerShop.sneaker.id),
isOrdered = viewModel.isOrdered(sneakerShop.sneaker.id),
onClick = { /* переход */ },
// onFavoriteClick = { viewModel.toggleLike(sneakerShop.sneaker.id) }
onFavoriteClick = {
viewModel.toggleLike(sneakerShop.sneaker.id)
val likedCount = viewModel.likedSneakers.values.count { it }
Log.d("SneakerLike", "Лайкнуто кроссовок: $likedCount")
},
onOrderClick = {
val sneakerId = sneakerShop.sneaker.id
if (!viewModel.isOrdered(sneakerId)) {
viewModel.toggleOrder(sneakerId)
viewModel.setSneakerQuantity(sneakerId, 1)
} else {
viewModel.toggleOrder(sneakerId)
viewModel.setSneakerQuantity(sneakerId, 0)
}
val orderCount = viewModel.orderedSneakers.values.count { it }
Log.d("SneakerOrder", "Добавлено в корзину кроссовок: $orderCount")
}
)
}
@ -337,11 +346,11 @@ fun SneakerSelection(sneakers: List<SneakerShop>, viewModel: MainViewModel) {
fun SneakerButton(
sneakerShop: SneakerShop,
isLiked: Boolean,
isOrdered: Boolean,
onClick: () -> Unit,
onFavoriteClick: () -> Unit
onFavoriteClick: () -> Unit,
onOrderClick: () -> Unit
) {
var isAddedToCart by remember { mutableStateOf(false) }
Box(modifier = Modifier.padding(8.dp)) {
Button(
onClick = onClick,
@ -376,17 +385,14 @@ fun SneakerButton(
val icon = if (isLiked) Icons.Default.Favorite else Icons.Default.FavoriteBorder
Icon(imageVector = icon, contentDescription = "Favourite", tint = Color.Red)
}
IconButton(
onClick = {
isAddedToCart = !isAddedToCart
},
onClick = onOrderClick,
modifier = Modifier
.align(Alignment.BottomEnd)
.size(32.dp)
.padding(4.dp)
) {
val iconRes = if (isAddedToCart) Icons.Default.ShoppingCart else Icons.Default.Add
val iconRes = if (isOrdered) Icons.Default.ShoppingCart else Icons.Default.Add
Icon(imageVector = iconRes, contentDescription = "Cart", tint = Color.Cyan)
}
}

View File

@ -45,7 +45,7 @@ fun CategorySneakersContent(
viewModel.sneakersCategories.sneakers.filter {
viewModel.selectedCategory == null || it.categoryId == viewModel.selectedCategory?.id
}.map { sneaker ->
SneakerShop(sneaker, false)
SneakerShop(sneaker, false, false)
}
)
}

View File

@ -0,0 +1,45 @@
package com.example.testktor.screen.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.navigation.NavController
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.method.main.SneakerCategorySelection
import com.example.testktor.method.main.TopWriteNavigationBar
@Composable
fun FavouriteSneakersScreen(
navController: NavController,
viewModel: MainViewModel
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F7F9))
) {
FavouriteSneakersContent(navController, viewModel)
}
}
@Composable
fun FavouriteSneakersContent(
navController: NavController,
viewModel: MainViewModel
) {
val likedSneakers = remember(viewModel.selectedCategory, viewModel.likedSneakers) {
viewModel.getLikedSneakersByCategory()
}
Column {
TopWriteNavigationBar(navController, "Избранное", viewModel)
SneakerCategorySelection(likedSneakers, viewModel)
}
}

View File

@ -0,0 +1,49 @@
package com.example.testktor.screen.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.navigation.NavController
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.method.main.BottomKorzinaNavigationBar
import com.example.testktor.method.main.SneakerKorzinaSelection
import com.example.testktor.method.main.TopWriteNavigationBar
@Composable
fun KorzinaSneakersScreen(
navController: NavController,
viewModel: MainViewModel
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F7F9))
) {
KorzinaSneakersContent(navController, viewModel)
}
}
@Composable
fun KorzinaSneakersContent(
navController: NavController,
viewModel: MainViewModel
) {
val orderedSneakers by remember(viewModel.orderedSneakers, viewModel.selectedCategory) {
derivedStateOf { viewModel.getOrderedSneakersByCategory() }
}
Column {
TopWriteNavigationBar(navController, "Корзина", viewModel)
SneakerKorzinaSelection(sneakers = orderedSneakers, viewModel = viewModel)
BottomKorzinaNavigationBar(navController, orderedSneakers, viewModel)
}
}

View File

@ -52,7 +52,7 @@ fun SneakersContent(
var sneakers by remember(viewModel.sneakersCategories) {
mutableStateOf(
viewModel.sneakersCategories.sneakers.map { sneaker ->
SneakerShop(sneaker, false)
SneakerShop(sneaker, false, false)
}
)
}
@ -84,6 +84,6 @@ fun SneakersContent(
OverSmth("Популярное", navController)
SneakerSelection(sneakers = sneakers, viewModel)
SalesBar()
BottomNavigationBar()
BottomNavigationBar(navController)
}
}

View File

@ -27,19 +27,19 @@ fun PopularSneakersScreen(
.fillMaxSize()
.background(Color(0xFFF7F7F9))
) {
FavouriteSneakersContent(navController, viewModel)
PopularSneakersContent(navController, viewModel)
}
}
@Composable
fun FavouriteSneakersContent(
fun PopularSneakersContent(
navController: NavController,
viewModel: MainViewModel
) {
var sneakers by remember(viewModel.sneakersCategories) {
mutableStateOf(
viewModel.sneakersCategories.sneakers.map { sneaker ->
SneakerShop(sneaker, false)
SneakerShop(sneaker, false, false)
}
)
}

View File

@ -0,0 +1,40 @@
package com.example.testktor.screen.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.method.main.TopWriteNavigationBar
@Composable
fun SuccessOrderSneakersScreen(
navController: NavController,
viewModel: MainViewModel
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F7F9))
) {
SuccessOrderSneakersContent(navController, viewModel)
}
}
@Composable
fun SuccessOrderSneakersContent(
navController: NavController,
viewModel: MainViewModel
){
Column {
TopWriteNavigationBar(navController, "Success order", viewModel)
Text(text = "Заказ оформлен", fontSize = 30.sp)
}
}